package orchestrator

import (
	"context"
	"github.com/tech/sendico/pkg/discovery"
	"strings"
	"testing"

	chainclient "github.com/tech/sendico/gateway/chain/client"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/sexec"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/xplan"
	"github.com/tech/sendico/payments/storage/model"
	pm "github.com/tech/sendico/pkg/model"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	"go.mongodb.org/mongo-driver/v2/bson"
)

func TestGatewayGuardExecutor_ExecuteGuard_LiquidityProbePasses(t *testing.T) {
	orgID := bson.NewObjectID()
	var walletReq *chainv1.GetWalletBalanceRequest
	executor := &gatewayGuardExecutor{
		gatewayInvokeResolver: &fakeGatewayInvokeResolver{
			client: &chainclient.Fake{
				GetWalletBalanceFn: func(_ context.Context, req *chainv1.GetWalletBalanceRequest) (*chainv1.GetWalletBalanceResponse, error) {
					walletReq = req
					return &chainv1.GetWalletBalanceResponse{
						Balance: &chainv1.WalletBalance{
							Available: &moneyv1.Money{Amount: "5", Currency: "USDT"},
						},
					}, nil
				},
			},
		},
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "crypto_1",
					InstanceID: "crypto_1",
					Rail:       discovery.RailCrypto,
					InvokeURI:  "grpc://crypto-gateway",
					IsEnabled:  true,
				},
			},
		},
	}

	out, err := executor.ExecuteGuard(context.Background(), sexec.StepRequest{
		Payment: testLiquidityProbePayment(orgID, "wallet-src", "1.00", "USDT", paymenttypes.QuoteExecutionReadinessLiquidityReady),
		Step: xplan.Step{
			StepRef:  xplan.QuoteReadinessGuardStepRef,
			StepCode: string(xplan.GuardOperationQuoteReadinessGuard),
			Kind:     xplan.StepKindLiquidityCheck,
		},
		StepExecution: agg.StepExecution{
			StepRef:  xplan.QuoteReadinessGuardStepRef,
			StepCode: string(xplan.GuardOperationQuoteReadinessGuard),
			Attempt:  1,
		},
	})
	if err != nil {
		t.Fatalf("ExecuteGuard returned error: %v", err)
	}
	if out == nil {
		t.Fatal("expected output")
	}
	if got, want := out.StepExecution.State, agg.StepStateCompleted; got != want {
		t.Fatalf("state mismatch: got=%q want=%q", got, want)
	}
	if walletReq == nil {
		t.Fatal("expected wallet balance request")
	}
	if got, want := walletReq.GetWalletRef(), "wallet-src"; got != want {
		t.Fatalf("wallet_ref mismatch: got=%q want=%q", got, want)
	}
}

func TestGatewayGuardExecutor_ExecuteGuard_InsufficientLiquidity(t *testing.T) {
	orgID := bson.NewObjectID()
	executor := &gatewayGuardExecutor{
		gatewayInvokeResolver: &fakeGatewayInvokeResolver{
			client: &chainclient.Fake{
				GetWalletBalanceFn: func(_ context.Context, _ *chainv1.GetWalletBalanceRequest) (*chainv1.GetWalletBalanceResponse, error) {
					return &chainv1.GetWalletBalanceResponse{
						Balance: &chainv1.WalletBalance{
							Available: &moneyv1.Money{Amount: "0.5", Currency: "USDT"},
						},
					}, nil
				},
			},
		},
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "crypto_1",
					InstanceID: "crypto_1",
					Rail:       discovery.RailCrypto,
					InvokeURI:  "grpc://crypto-gateway",
					IsEnabled:  true,
				},
			},
		},
	}

	out, err := executor.ExecuteGuard(context.Background(), sexec.StepRequest{
		Payment: testLiquidityProbePayment(orgID, "wallet-src", "1.00", "USDT", paymenttypes.QuoteExecutionReadinessLiquidityReady),
		Step: xplan.Step{
			StepRef:  xplan.QuoteReadinessGuardStepRef,
			StepCode: string(xplan.GuardOperationQuoteReadinessGuard),
			Kind:     xplan.StepKindLiquidityCheck,
		},
		StepExecution: agg.StepExecution{
			StepRef:  xplan.QuoteReadinessGuardStepRef,
			StepCode: string(xplan.GuardOperationQuoteReadinessGuard),
			Attempt:  1,
		},
	})
	if err != nil {
		t.Fatalf("ExecuteGuard returned error: %v", err)
	}
	if out == nil {
		t.Fatal("expected output")
	}
	if got, want := out.StepExecution.State, agg.StepStateFailed; got != want {
		t.Fatalf("state mismatch: got=%q want=%q", got, want)
	}
	if got, want := out.StepExecution.FailureCode, "guard.liquidity_insufficient"; got != want {
		t.Fatalf("failure code mismatch: got=%q want=%q", got, want)
	}
	if !strings.Contains(out.StepExecution.FailureMsg, "available=0.5") {
		t.Fatalf("expected failure message to include available balance, got=%q", out.StepExecution.FailureMsg)
	}
}

func TestGatewayGuardExecutor_ExecuteGuard_ReadinessStopsBeforeProbe(t *testing.T) {
	orgID := bson.NewObjectID()
	probeCalls := 0
	executor := &gatewayGuardExecutor{
		gatewayInvokeResolver: &fakeGatewayInvokeResolver{
			client: &chainclient.Fake{
				GetWalletBalanceFn: func(_ context.Context, _ *chainv1.GetWalletBalanceRequest) (*chainv1.GetWalletBalanceResponse, error) {
					probeCalls++
					return &chainv1.GetWalletBalanceResponse{}, nil
				},
			},
		},
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "crypto_1",
					InstanceID: "crypto_1",
					Rail:       discovery.RailCrypto,
					InvokeURI:  "grpc://crypto-gateway",
					IsEnabled:  true,
				},
			},
		},
	}

	out, err := executor.ExecuteGuard(context.Background(), sexec.StepRequest{
		Payment: testLiquidityProbePayment(orgID, "wallet-src", "1.00", "USDT", paymenttypes.QuoteExecutionReadinessLiquidityObtainable),
		Step: xplan.Step{
			StepRef:  xplan.QuoteReadinessGuardStepRef,
			StepCode: string(xplan.GuardOperationQuoteReadinessGuard),
			Kind:     xplan.StepKindLiquidityCheck,
		},
		StepExecution: agg.StepExecution{
			StepRef:  xplan.QuoteReadinessGuardStepRef,
			StepCode: string(xplan.GuardOperationQuoteReadinessGuard),
			Attempt:  1,
		},
	})
	if err != nil {
		t.Fatalf("ExecuteGuard returned error: %v", err)
	}
	if out == nil {
		t.Fatal("expected output")
	}
	if got, want := out.StepExecution.FailureCode, "guard.liquidity_not_ready"; got != want {
		t.Fatalf("failure code mismatch: got=%q want=%q", got, want)
	}
	if probeCalls != 0 {
		t.Fatalf("expected no probe calls when readiness is not ready, got=%d", probeCalls)
	}
}

func testLiquidityProbePayment(
	orgID bson.ObjectID,
	walletRef string,
	amount string,
	currency string,
	readiness paymenttypes.QuoteExecutionReadiness,
) *agg.Payment {
	return &agg.Payment{
		OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
		PaymentRef:            "payment-guard",
		IntentSnapshot: model.PaymentIntent{
			Source: model.PaymentEndpoint{
				Type: model.EndpointTypeManagedWallet,
				ManagedWallet: &model.ManagedWalletEndpoint{
					ManagedWalletRef: walletRef,
				},
			},
			Amount: &paymenttypes.Money{
				Amount:   amount,
				Currency: currency,
			},
		},
		QuoteSnapshot: &model.PaymentQuoteSnapshot{
			DebitAmount: &paymenttypes.Money{
				Amount:   amount,
				Currency: currency,
			},
			ExecutionConditions: &paymenttypes.QuoteExecutionConditions{
				Readiness:                         readiness,
				LiquidityCheckRequiredAtExecution: true,
			},
			Route: &paymenttypes.QuoteRouteSpecification{
				Hops: []*paymenttypes.QuoteRouteHop{
					{
						Index:      1,
						Rail:       "CRYPTO",
						Gateway:    "crypto_1",
						InstanceID: "crypto_1",
						Role:       paymenttypes.QuoteRouteHopRoleSource,
					},
					{
						Index:      2,
						Rail:       "CARD",
						Gateway:    paymenttypes.DefaultCardsGatewayID,
						InstanceID: paymenttypes.DefaultCardsGatewayID,
						Role:       paymenttypes.QuoteRouteHopRoleDestination,
					},
				},
			},
		},
	}
}
