package orchestrator

import (
	"context"
	"github.com/tech/sendico/pkg/discovery"
	"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/erecon"
	"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"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	"go.mongodb.org/mongo-driver/v2/bson"
)

func TestGatewayCryptoExecutor_ExecuteCrypto_SubmitsTransfer(t *testing.T) {
	orgID := bson.NewObjectID()

	var submitReq *chainv1.SubmitTransferRequest
	client := &chainclient.Fake{
		SubmitTransferFn: func(_ context.Context, req *chainv1.SubmitTransferRequest) (*chainv1.SubmitTransferResponse, error) {
			submitReq = req
			return &chainv1.SubmitTransferResponse{
				Transfer: &chainv1.Transfer{
					TransferRef:  "trf-1",
					OperationRef: "op-1",
				},
			}, nil
		},
	}
	resolver := &fakeGatewayInvokeResolver{client: client}
	registry := &fakeGatewayRegistry{
		items: []*model.GatewayInstanceDescriptor{
			{
				ID:         "crypto_rail_gateway_arbitrum_sepolia",
				InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
				Rail:       discovery.RailCrypto,
				InvokeURI:  "grpc://crypto-gateway",
				IsEnabled:  true,
			},
		},
	}
	executor := &gatewayCryptoExecutor{
		gatewayInvokeResolver: resolver,
		gatewayRegistry:       registry,
		cardGatewayRoutes: map[string]CardGatewayRoute{
			paymenttypes.DefaultCardsGatewayID: {FundingAddress: "TUA_DEST"},
		},
	}

	req := sexec.StepRequest{
		Payment: &agg.Payment{
			OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
			PaymentRef:            "payment-1",
			IdempotencyKey:        "idem-1",
			IntentSnapshot: model.PaymentIntent{
				Ref: "intent-1",
				Source: model.PaymentEndpoint{
					Type: model.EndpointTypeManagedWallet,
					ManagedWallet: &model.ManagedWalletEndpoint{
						ManagedWalletRef: "wallet-src",
					},
				},
				Destination: model.PaymentEndpoint{
					Type: model.EndpointTypeCard,
					Card: &model.CardEndpoint{Pan: "4111111111111111"},
				},
				Amount: &paymenttypes.Money{Amount: "1", Currency: "USDT"},
			},
			QuoteSnapshot: &model.PaymentQuoteSnapshot{
				DebitAmount: &paymenttypes.Money{Amount: "1.000000", Currency: "USDT"},
				Route: &paymenttypes.QuoteRouteSpecification{
					Hops: []*paymenttypes.QuoteRouteHop{
						{Index: 1, Rail: "CRYPTO", Gateway: "crypto_rail_gateway_arbitrum_sepolia", InstanceID: "crypto_rail_gateway_arbitrum_sepolia", Role: paymenttypes.QuoteRouteHopRoleSource},
						{Index: 4, Rail: "CARD", Gateway: paymenttypes.DefaultCardsGatewayID, InstanceID: paymenttypes.DefaultCardsGatewayID, Role: paymenttypes.QuoteRouteHopRoleDestination},
					},
				},
			},
		},
		Step: xplan.Step{
			StepRef:    "hop_1_crypto_send",
			StepCode:   "hop.1.crypto.send",
			Action:     discovery.RailOperationSend,
			Rail:       discovery.RailCrypto,
			Gateway:    "crypto_rail_gateway_arbitrum_sepolia",
			InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
		},
		StepExecution: agg.StepExecution{
			StepRef:  "hop_1_crypto_send",
			StepCode: "hop.1.crypto.send",
			Attempt:  1,
		},
	}

	out, err := executor.ExecuteCrypto(context.Background(), req)
	if err != nil {
		t.Fatalf("ExecuteCrypto returned error: %v", err)
	}
	if out == nil {
		t.Fatal("expected output")
	}
	if out.StepExecution.State != agg.StepStateCompleted {
		t.Fatalf("expected completed state, got=%q", out.StepExecution.State)
	}
	if out.StepExecution.ExecutedMoney == nil {
		t.Fatal("expected executed money to be recorded")
	}
	if got, want := out.StepExecution.ExecutedMoney.Amount, "1.000000"; got != want {
		t.Fatalf("executed money amount mismatch: got=%q want=%q", got, want)
	}
	if got, want := out.StepExecution.ExecutedMoney.Currency, "USDT"; got != want {
		t.Fatalf("executed money currency mismatch: got=%q want=%q", got, want)
	}
	if submitReq == nil {
		t.Fatal("expected transfer submission request")
	}
	if got, want := submitReq.GetSourceWalletRef(), "wallet-src"; got != want {
		t.Fatalf("source wallet mismatch: got=%q want=%q", got, want)
	}
	if got, want := submitReq.GetAmount().GetAmount(), "1.000000"; got != want {
		t.Fatalf("amount mismatch: got=%q want=%q", got, want)
	}
	if got, want := submitReq.GetDestination().GetExternalAddress(), "TUA_DEST"; got != want {
		t.Fatalf("destination mismatch: got=%q want=%q", got, want)
	}
	if got, want := resolver.lastInvokeURI, "grpc://crypto-gateway"; got != want {
		t.Fatalf("invoke uri mismatch: got=%q want=%q", got, want)
	}
	if len(out.StepExecution.ExternalRefs) != 2 {
		t.Fatalf("expected two external refs, got=%d", len(out.StepExecution.ExternalRefs))
	}
	if out.StepExecution.ExternalRefs[0].Kind != erecon.ExternalRefKindOperation {
		t.Fatalf("unexpected first external ref kind: %q", out.StepExecution.ExternalRefs[0].Kind)
	}
}

func TestGatewayCryptoExecutor_ExecuteCrypto_AttachesCommentForDestinationHop(t *testing.T) {
	orgID := bson.NewObjectID()

	var submitReq *chainv1.SubmitTransferRequest
	client := &chainclient.Fake{
		SubmitTransferFn: func(_ context.Context, req *chainv1.SubmitTransferRequest) (*chainv1.SubmitTransferResponse, error) {
			submitReq = req
			return &chainv1.SubmitTransferResponse{
				Transfer: &chainv1.Transfer{
					TransferRef:  "trf-comment",
					OperationRef: "op-comment",
				},
			}, nil
		},
	}
	executor := &gatewayCryptoExecutor{
		gatewayInvokeResolver: &fakeGatewayInvokeResolver{client: client},
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "crypto_rail_gateway_arbitrum_sepolia",
					InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
					Rail:       discovery.RailCrypto,
					InvokeURI:  "grpc://crypto-gateway",
					IsEnabled:  true,
				},
			},
		},
	}

	out, err := executor.ExecuteCrypto(context.Background(), sexec.StepRequest{
		Payment: &agg.Payment{
			OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
			PaymentRef:            "payment-comment",
			IdempotencyKey:        "idem-comment",
			IntentSnapshot: model.PaymentIntent{
				Ref:     "intent-comment",
				Comment: "invoice-crypto-7",
				Source: model.PaymentEndpoint{
					Type: model.EndpointTypeManagedWallet,
					ManagedWallet: &model.ManagedWalletEndpoint{
						ManagedWalletRef: "wallet-src",
					},
				},
				Destination: model.PaymentEndpoint{
					Type: model.EndpointTypeExternalChain,
					ExternalChain: &model.ExternalChainEndpoint{
						Address: "TXYZdst",
					},
				},
				Amount: &paymenttypes.Money{Amount: "1", Currency: "USDT"},
			},
		},
		Step: xplan.Step{
			StepRef:    "hop_2_crypto_send",
			StepCode:   "hop.2.crypto.send",
			Action:     discovery.RailOperationSend,
			Rail:       discovery.RailCrypto,
			Gateway:    "crypto_rail_gateway_arbitrum_sepolia",
			InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
			HopRole:    paymenttypes.QuoteRouteHopRoleDestination,
		},
		StepExecution: agg.StepExecution{
			StepRef:  "hop_2_crypto_send",
			StepCode: "hop.2.crypto.send",
			Attempt:  1,
		},
	})
	if err != nil {
		t.Fatalf("ExecuteCrypto returned error: %v", err)
	}
	if submitReq == nil {
		t.Fatal("expected transfer submission request")
	}
	if out == nil {
		t.Fatal("expected output")
	}
	if got, want := out.StepExecution.Comment, "invoice-crypto-7"; got != want {
		t.Fatalf("step comment mismatch: got=%q want=%q", got, want)
	}
}

func TestGatewayCryptoExecutor_ExecuteCrypto_UsesPlannedMoney(t *testing.T) {
	orgID := bson.NewObjectID()

	var submitReq *chainv1.SubmitTransferRequest
	client := &chainclient.Fake{
		SubmitTransferFn: func(_ context.Context, req *chainv1.SubmitTransferRequest) (*chainv1.SubmitTransferResponse, error) {
			submitReq = req
			return &chainv1.SubmitTransferResponse{
				Transfer: &chainv1.Transfer{
					TransferRef:  "trf-planned",
					OperationRef: "op-planned",
				},
			}, nil
		},
	}
	executor := &gatewayCryptoExecutor{
		gatewayInvokeResolver: &fakeGatewayInvokeResolver{client: client},
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "crypto_rail_gateway_arbitrum_sepolia",
					InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
					Rail:       discovery.RailCrypto,
					InvokeURI:  "grpc://crypto-gateway",
					IsEnabled:  true,
				},
			},
		},
		cardGatewayRoutes: map[string]CardGatewayRoute{
			paymenttypes.DefaultCardsGatewayID: {FundingAddress: "TUA_DEST"},
		},
	}

	_, err := executor.ExecuteCrypto(context.Background(), sexec.StepRequest{
		Payment: &agg.Payment{
			OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
			PaymentRef:            "payment-planned",
			IdempotencyKey:        "idem-planned",
			IntentSnapshot: model.PaymentIntent{
				Ref: "intent-planned",
				Source: model.PaymentEndpoint{
					Type: model.EndpointTypeManagedWallet,
					ManagedWallet: &model.ManagedWalletEndpoint{
						ManagedWalletRef: "wallet-src",
					},
				},
				Destination: model.PaymentEndpoint{
					Type: model.EndpointTypeCard,
					Card: &model.CardEndpoint{Pan: "4111111111111111"},
				},
				Amount: &paymenttypes.Money{Amount: "1", Currency: "USDT"},
			},
			QuoteSnapshot: &model.PaymentQuoteSnapshot{
				DebitAmount: &paymenttypes.Money{Amount: "1.000000", Currency: "USDT"},
				Route: &paymenttypes.QuoteRouteSpecification{
					Hops: []*paymenttypes.QuoteRouteHop{
						{Index: 1, Rail: "CRYPTO", Gateway: "crypto_rail_gateway_arbitrum_sepolia", InstanceID: "crypto_rail_gateway_arbitrum_sepolia", Role: paymenttypes.QuoteRouteHopRoleSource},
						{Index: 4, Rail: "CARD", Gateway: paymenttypes.DefaultCardsGatewayID, InstanceID: paymenttypes.DefaultCardsGatewayID, Role: paymenttypes.QuoteRouteHopRoleDestination},
					},
				},
			},
		},
		Step: xplan.Step{
			StepRef:    "hop_1_crypto_send",
			StepCode:   "hop.1.crypto.send",
			Action:     discovery.RailOperationSend,
			Rail:       discovery.RailCrypto,
			Gateway:    "crypto_rail_gateway_arbitrum_sepolia",
			InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
		},
		StepExecution: agg.StepExecution{
			StepRef:      "hop_1_crypto_send",
			StepCode:     "hop.1.crypto.send",
			Attempt:      1,
			PlannedMoney: &paymenttypes.Money{Amount: "5.50", Currency: "USDT"},
		},
	})
	if err != nil {
		t.Fatalf("ExecuteCrypto returned error: %v", err)
	}
	if submitReq == nil {
		t.Fatal("expected transfer submission request")
	}
	if got, want := submitReq.GetAmount().GetAmount(), "5.50"; got != want {
		t.Fatalf("amount mismatch: got=%q want=%q", got, want)
	}
	if got, want := submitReq.GetAmount().GetCurrency(), "USDT"; got != want {
		t.Fatalf("currency mismatch: got=%q want=%q", got, want)
	}
}

func TestGatewayCryptoExecutor_ExecuteCrypto_MissingCardRoute(t *testing.T) {
	orgID := bson.NewObjectID()
	executor := &gatewayCryptoExecutor{
		gatewayInvokeResolver: &fakeGatewayInvokeResolver{
			client: &chainclient.Fake{},
		},
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "crypto_1",
					InstanceID: "crypto_1",
					Rail:       discovery.RailCrypto,
					InvokeURI:  "grpc://crypto-gateway",
					IsEnabled:  true,
				},
			},
		},
		cardGatewayRoutes: map[string]CardGatewayRoute{},
	}

	_, err := executor.ExecuteCrypto(context.Background(), sexec.StepRequest{
		Payment: &agg.Payment{
			OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
			PaymentRef:            "payment-2",
			IdempotencyKey:        "idem-2",
			IntentSnapshot: model.PaymentIntent{
				Source: model.PaymentEndpoint{
					Type: model.EndpointTypeManagedWallet,
					ManagedWallet: &model.ManagedWalletEndpoint{
						ManagedWalletRef: "wallet-src",
					},
				},
				Destination: model.PaymentEndpoint{
					Type: model.EndpointTypeCard,
					Card: &model.CardEndpoint{Pan: "4111111111111111"},
				},
				Amount: &paymenttypes.Money{Amount: "1", Currency: "USDT"},
			},
			QuoteSnapshot: &model.PaymentQuoteSnapshot{
				Route: &paymenttypes.QuoteRouteSpecification{
					Hops: []*paymenttypes.QuoteRouteHop{
						{Index: 1, Rail: "CRYPTO", Gateway: "crypto_1", InstanceID: "crypto_1", Role: paymenttypes.QuoteRouteHopRoleSource},
						{Index: 4, Rail: "CARD", Gateway: paymenttypes.DefaultCardsGatewayID, InstanceID: paymenttypes.DefaultCardsGatewayID, Role: paymenttypes.QuoteRouteHopRoleDestination},
					},
				},
			},
		},
		Step: xplan.Step{
			StepRef:    "hop_1_crypto_send",
			StepCode:   "hop.1.crypto.send",
			Action:     discovery.RailOperationSend,
			Rail:       discovery.RailCrypto,
			Gateway:    "crypto_1",
			InstanceID: "crypto_1",
		},
		StepExecution: agg.StepExecution{
			StepRef:  "hop_1_crypto_send",
			StepCode: "hop.1.crypto.send",
			Attempt:  1,
		},
	})
	if err == nil {
		t.Fatal("expected error for missing card route")
	}
}

func TestGatewayCryptoExecutor_ExecuteCrypto_SendDoesNotSubmitWalletFeeTransfer(t *testing.T) {
	orgID := bson.NewObjectID()

	submitRequests := make([]*chainv1.SubmitTransferRequest, 0, 1)
	client := &chainclient.Fake{
		SubmitTransferFn: func(_ context.Context, req *chainv1.SubmitTransferRequest) (*chainv1.SubmitTransferResponse, error) {
			submitRequests = append(submitRequests, req)
			return &chainv1.SubmitTransferResponse{
				Transfer: &chainv1.Transfer{
					TransferRef:  "trf-principal",
					OperationRef: "op-principal",
				},
			}, nil
		},
	}
	resolver := &fakeGatewayInvokeResolver{client: client}
	registry := &fakeGatewayRegistry{
		items: []*model.GatewayInstanceDescriptor{
			{
				ID:         "crypto_rail_gateway_arbitrum_sepolia",
				InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
				Rail:       discovery.RailCrypto,
				InvokeURI:  "grpc://crypto-gateway",
				IsEnabled:  true,
			},
		},
	}
	executor := &gatewayCryptoExecutor{
		gatewayInvokeResolver: resolver,
		gatewayRegistry:       registry,
		cardGatewayRoutes: map[string]CardGatewayRoute{
			paymenttypes.DefaultCardsGatewayID: {FundingAddress: "TUA_DEST", FeeAddress: "TUA_FEE"},
		},
	}

	req := sexec.StepRequest{
		Payment: &agg.Payment{
			OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
			PaymentRef:            "payment-1",
			IdempotencyKey:        "idem-1",
			IntentSnapshot: model.PaymentIntent{
				Ref: "intent-1",
				Source: model.PaymentEndpoint{
					Type: model.EndpointTypeManagedWallet,
					ManagedWallet: &model.ManagedWalletEndpoint{
						ManagedWalletRef: "wallet-src",
					},
				},
				Destination: model.PaymentEndpoint{
					Type: model.EndpointTypeCard,
					Card: &model.CardEndpoint{Pan: "4111111111111111"},
				},
				Amount: &paymenttypes.Money{Amount: "10", Currency: "USDT"},
			},
			QuoteSnapshot: &model.PaymentQuoteSnapshot{
				DebitAmount: &paymenttypes.Money{Amount: "10.000000", Currency: "USDT"},
				FeeLines: []*paymenttypes.FeeLine{
					{
						Money:    &paymenttypes.Money{Amount: "0.70", Currency: "USDT"},
						LineType: paymenttypes.PostingLineTypeFee,
						Side:     paymenttypes.EntrySideDebit,
						Meta:     map[string]string{"fee_target": "wallet"},
					},
				},
				Route: &paymenttypes.QuoteRouteSpecification{
					Hops: []*paymenttypes.QuoteRouteHop{
						{Index: 1, Rail: "CRYPTO", Gateway: "crypto_rail_gateway_arbitrum_sepolia", InstanceID: "crypto_rail_gateway_arbitrum_sepolia", Role: paymenttypes.QuoteRouteHopRoleSource},
						{Index: 4, Rail: "CARD", Gateway: paymenttypes.DefaultCardsGatewayID, InstanceID: paymenttypes.DefaultCardsGatewayID, Role: paymenttypes.QuoteRouteHopRoleDestination},
					},
				},
			},
		},
		Step: xplan.Step{
			StepRef:    "hop_1_crypto_send",
			StepCode:   "hop.1.crypto.send",
			Action:     discovery.RailOperationSend,
			Rail:       discovery.RailCrypto,
			Gateway:    "crypto_rail_gateway_arbitrum_sepolia",
			InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
		},
		StepExecution: agg.StepExecution{
			StepRef:  "hop_1_crypto_send",
			StepCode: "hop.1.crypto.send",
			Attempt:  1,
		},
	}

	out, err := executor.ExecuteCrypto(context.Background(), req)
	if err != nil {
		t.Fatalf("ExecuteCrypto returned error: %v", err)
	}
	if out == nil {
		t.Fatal("expected output")
	}
	if got, want := len(submitRequests), 1; got != want {
		t.Fatalf("submit transfer calls mismatch: got=%d want=%d", got, want)
	}

	principalReq := submitRequests[0]
	if got, want := principalReq.GetAmount().GetAmount(), "10.000000"; got != want {
		t.Fatalf("principal amount mismatch: got=%q want=%q", got, want)
	}
	if got, want := principalReq.GetDestination().GetExternalAddress(), "TUA_DEST"; got != want {
		t.Fatalf("principal destination mismatch: got=%q want=%q", got, want)
	}
}

func TestGatewayCryptoExecutor_ExecuteCrypto_FeeActionResolvesFeeAddressFromFeeWalletRef(t *testing.T) {
	orgID := bson.NewObjectID()

	submitRequests := make([]*chainv1.SubmitTransferRequest, 0, 1)
	var managedWalletReq *chainv1.GetManagedWalletRequest
	client := &chainclient.Fake{
		GetManagedWalletFn: func(_ context.Context, req *chainv1.GetManagedWalletRequest) (*chainv1.GetManagedWalletResponse, error) {
			managedWalletReq = req
			return &chainv1.GetManagedWalletResponse{
				Wallet: &chainv1.ManagedWallet{
					WalletRef:      "fee-wallet-ref",
					DepositAddress: "TUA_FEE_FROM_WALLET",
				},
			}, nil
		},
		SubmitTransferFn: func(_ context.Context, req *chainv1.SubmitTransferRequest) (*chainv1.SubmitTransferResponse, error) {
			submitRequests = append(submitRequests, req)
			return &chainv1.SubmitTransferResponse{
				Transfer: &chainv1.Transfer{
					TransferRef:  "trf-fee",
					OperationRef: "op-fee",
				},
			}, nil
		},
	}
	resolver := &fakeGatewayInvokeResolver{client: client}
	registry := &fakeGatewayRegistry{
		items: []*model.GatewayInstanceDescriptor{
			{
				ID:         "crypto_rail_gateway_arbitrum_sepolia",
				InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
				Rail:       discovery.RailCrypto,
				InvokeURI:  "grpc://crypto-gateway",
				IsEnabled:  true,
			},
		},
	}
	executor := &gatewayCryptoExecutor{
		gatewayInvokeResolver: resolver,
		gatewayRegistry:       registry,
		cardGatewayRoutes: map[string]CardGatewayRoute{
			paymenttypes.DefaultCardsGatewayID: {FundingAddress: "TUA_DEST", FeeWalletRef: "fee-wallet-ref"},
		},
	}

	req := sexec.StepRequest{
		Payment: &agg.Payment{
			OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
			PaymentRef:            "payment-1",
			IdempotencyKey:        "idem-1",
			IntentSnapshot: model.PaymentIntent{
				Ref: "intent-1",
				Source: model.PaymentEndpoint{
					Type: model.EndpointTypeManagedWallet,
					ManagedWallet: &model.ManagedWalletEndpoint{
						ManagedWalletRef: "wallet-src",
					},
				},
				Destination: model.PaymentEndpoint{
					Type: model.EndpointTypeCard,
					Card: &model.CardEndpoint{Pan: "4111111111111111"},
				},
				Amount: &paymenttypes.Money{Amount: "10", Currency: "USDT"},
			},
			QuoteSnapshot: &model.PaymentQuoteSnapshot{
				DebitAmount: &paymenttypes.Money{Amount: "10.000000", Currency: "USDT"},
				FeeLines: []*paymenttypes.FeeLine{
					{
						Money:    &paymenttypes.Money{Amount: "0.70", Currency: "USDT"},
						LineType: paymenttypes.PostingLineTypeFee,
						Side:     paymenttypes.EntrySideDebit,
						Meta:     map[string]string{"fee_target": "wallet"},
					},
				},
				Route: &paymenttypes.QuoteRouteSpecification{
					Hops: []*paymenttypes.QuoteRouteHop{
						{Index: 1, Rail: "CRYPTO", Gateway: "crypto_rail_gateway_arbitrum_sepolia", InstanceID: "crypto_rail_gateway_arbitrum_sepolia", Role: paymenttypes.QuoteRouteHopRoleSource},
						{Index: 4, Rail: "CARD", Gateway: paymenttypes.DefaultCardsGatewayID, InstanceID: paymenttypes.DefaultCardsGatewayID, Role: paymenttypes.QuoteRouteHopRoleDestination},
					},
				},
			},
		},
		Step: xplan.Step{
			StepRef:    "hop_1_crypto_fee",
			StepCode:   "hop.1.crypto.fee",
			Action:     discovery.RailOperationFee,
			Rail:       discovery.RailCrypto,
			Gateway:    "crypto_rail_gateway_arbitrum_sepolia",
			InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
		},
		StepExecution: agg.StepExecution{
			StepRef:  "hop_1_crypto_fee",
			StepCode: "hop.1.crypto.fee",
			Attempt:  1,
		},
	}

	_, err := executor.ExecuteCrypto(context.Background(), req)
	if err != nil {
		t.Fatalf("ExecuteCrypto returned error: %v", err)
	}
	if managedWalletReq == nil {
		t.Fatal("expected managed wallet lookup request")
	}
	if got, want := managedWalletReq.GetWalletRef(), "fee-wallet-ref"; got != want {
		t.Fatalf("fee wallet ref lookup mismatch: got=%q want=%q", got, want)
	}
	if got, want := len(submitRequests), 1; got != want {
		t.Fatalf("submit transfer calls mismatch: got=%d want=%d", got, want)
	}
	feeReq := submitRequests[0]
	if got, want := feeReq.GetAmount().GetAmount(), "0.7"; got != want {
		t.Fatalf("fee amount mismatch: got=%q want=%q", got, want)
	}
	if got, want := feeReq.GetDestination().GetExternalAddress(), "TUA_FEE_FROM_WALLET"; got != want {
		t.Fatalf("fee destination mismatch: got=%q want=%q", got, want)
	}
}

func TestGatewayCryptoExecutor_ExecuteCrypto_FeeActionUsesAllFeeLines(t *testing.T) {
	orgID := bson.NewObjectID()

	var submitReq *chainv1.SubmitTransferRequest
	client := &chainclient.Fake{
		SubmitTransferFn: func(_ context.Context, req *chainv1.SubmitTransferRequest) (*chainv1.SubmitTransferResponse, error) {
			submitReq = req
			return &chainv1.SubmitTransferResponse{
				Transfer: &chainv1.Transfer{
					TransferRef:  "trf-fee",
					OperationRef: "op-fee",
				},
			}, nil
		},
	}
	resolver := &fakeGatewayInvokeResolver{client: client}
	registry := &fakeGatewayRegistry{
		items: []*model.GatewayInstanceDescriptor{
			{
				ID:         "crypto_rail_gateway_arbitrum_sepolia",
				InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
				Rail:       discovery.RailCrypto,
				InvokeURI:  "grpc://crypto-gateway",
				IsEnabled:  true,
			},
		},
	}
	executor := &gatewayCryptoExecutor{
		gatewayInvokeResolver: resolver,
		gatewayRegistry:       registry,
		cardGatewayRoutes: map[string]CardGatewayRoute{
			paymenttypes.DefaultCardsGatewayID: {FundingAddress: "TUA_DEST", FeeAddress: "TUA_FEE"},
		},
	}

	req := sexec.StepRequest{
		Payment: &agg.Payment{
			OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
			PaymentRef:            "payment-1",
			IdempotencyKey:        "idem-1",
			IntentSnapshot: model.PaymentIntent{
				Ref: "intent-1",
				Source: model.PaymentEndpoint{
					Type: model.EndpointTypeManagedWallet,
					ManagedWallet: &model.ManagedWalletEndpoint{
						ManagedWalletRef: "wallet-src",
					},
				},
				Destination: model.PaymentEndpoint{
					Type: model.EndpointTypeCard,
					Card: &model.CardEndpoint{Pan: "4111111111111111"},
				},
				Amount: &paymenttypes.Money{Amount: "10", Currency: "USDT"},
			},
			QuoteSnapshot: &model.PaymentQuoteSnapshot{
				DebitAmount: &paymenttypes.Money{Amount: "10.000000", Currency: "USDT"},
				FeeLines: []*paymenttypes.FeeLine{
					{
						Money:    &paymenttypes.Money{Amount: "0.70", Currency: "USDT"},
						LineType: paymenttypes.PostingLineTypeFee,
						Side:     paymenttypes.EntrySideDebit,
						Meta:     map[string]string{"fee_target": "wallet"},
					},
					{
						Money:    &paymenttypes.Money{Amount: "0.30", Currency: "USDT"},
						LineType: paymenttypes.PostingLineTypeTax,
						Side:     paymenttypes.EntrySideDebit,
						Meta:     map[string]string{"fee_target": "ledger"},
					},
					{
						Money:    &paymenttypes.Money{Amount: "0.10", Currency: "USDT"},
						LineType: paymenttypes.PostingLineTypeReversal,
						Side:     paymenttypes.EntrySideCredit,
						Meta:     map[string]string{"fee_target": "rebate"},
					},
				},
				Route: &paymenttypes.QuoteRouteSpecification{
					Hops: []*paymenttypes.QuoteRouteHop{
						{Index: 1, Rail: "CRYPTO", Gateway: "crypto_rail_gateway_arbitrum_sepolia", InstanceID: "crypto_rail_gateway_arbitrum_sepolia", Role: paymenttypes.QuoteRouteHopRoleSource},
						{Index: 4, Rail: "CARD", Gateway: paymenttypes.DefaultCardsGatewayID, InstanceID: paymenttypes.DefaultCardsGatewayID, Role: paymenttypes.QuoteRouteHopRoleDestination},
					},
				},
			},
		},
		Step: xplan.Step{
			StepRef:    "hop_1_crypto_fee",
			StepCode:   "hop.1.crypto.fee",
			Action:     discovery.RailOperationFee,
			Rail:       discovery.RailCrypto,
			Gateway:    "crypto_rail_gateway_arbitrum_sepolia",
			InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
		},
		StepExecution: agg.StepExecution{
			StepRef:  "hop_1_crypto_fee",
			StepCode: "hop.1.crypto.fee",
			Attempt:  1,
		},
	}

	_, err := executor.ExecuteCrypto(context.Background(), req)
	if err != nil {
		t.Fatalf("ExecuteCrypto returned error: %v", err)
	}
	if submitReq == nil {
		t.Fatal("expected transfer submission")
	}
	if got, want := submitReq.GetAmount().GetAmount(), "0.9"; got != want {
		t.Fatalf("fee amount mismatch: got=%q want=%q", got, want)
	}
	if got, want := submitReq.GetDestination().GetExternalAddress(), "TUA_FEE"; got != want {
		t.Fatalf("fee destination mismatch: got=%q want=%q", got, want)
	}
}

type fakeGatewayInvokeResolver struct {
	lastInvokeURI string
	client        chainclient.Client
	err           error
}

func (f *fakeGatewayInvokeResolver) Resolve(_ context.Context, invokeURI string) (chainclient.Client, error) {
	f.lastInvokeURI = invokeURI
	if f.err != nil {
		return nil, f.err
	}
	return f.client, nil
}

type fakeGatewayRegistry struct {
	items []*model.GatewayInstanceDescriptor
	err   error
}

func (f *fakeGatewayRegistry) List(_ context.Context) ([]*model.GatewayInstanceDescriptor, error) {
	if f.err != nil {
		return nil, f.err
	}
	return f.items, nil
}
