package orchestrator

import (
	"context"
	"strings"
	"testing"

	mntxclient "github.com/tech/sendico/gateway/mntx/client"
	ledgerclient "github.com/tech/sendico/ledger/client"
	"github.com/tech/sendico/payments/orchestrator/storage/model"
	mo "github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/model/account_role"
	"github.com/tech/sendico/pkg/payments/rail"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
	mntxv1 "github.com/tech/sendico/pkg/proto/gateway/mntx/v1"
	ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
	orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/structpb"
)

func TestExecutePaymentPlan_SourceBeforeDestination(t *testing.T) {
	ctx := context.Background()

	store := newStubPaymentsStore()
	repo := &stubRepository{store: store}

	transferRefs := []string{"send-1", "fee-1"}
	sendCalls := 0
	railGateway := &fakeRailGateway{
		rail: "CRYPTO",
		sendFn: func(ctx context.Context, req rail.TransferRequest) (rail.RailResult, error) {
			ref := transferRefs[sendCalls]
			sendCalls++
			return rail.RailResult{ReferenceID: ref, Status: rail.TransferStatusWaiting}, nil
		},
	}

	moveCalls := 0
	pendingAccountID := "ledger:pending"
	operatingAccountID := "ledger:operating"
	transitAccountID := "ledger:transit"
	ledgerFake := &ledgerclient.Fake{
		ListConnectorAccountsFn: func(ctx context.Context, req *connectorv1.ListAccountsRequest) (*connectorv1.ListAccountsResponse, error) {
			details, _ := structpb.NewStruct(map[string]interface{}{
				"role": "ACCOUNT_ROLE_PENDING",
			})
			detailsOperating, _ := structpb.NewStruct(map[string]interface{}{
				"role": "ACCOUNT_ROLE_OPERATING",
			})
			detailsTransit, _ := structpb.NewStruct(map[string]interface{}{
				"role": "ACCOUNT_ROLE_TRANSIT",
			})
			return &connectorv1.ListAccountsResponse{
				Accounts: []*connectorv1.Account{
					{Ref: &connectorv1.AccountRef{ConnectorId: "ledger", AccountId: pendingAccountID}, Kind: connectorv1.AccountKind_LEDGER_ACCOUNT, Asset: "USDT", ProviderDetails: details},
					{Ref: &connectorv1.AccountRef{ConnectorId: "ledger", AccountId: operatingAccountID}, Kind: connectorv1.AccountKind_LEDGER_ACCOUNT, Asset: "USDT", ProviderDetails: detailsOperating},
					{Ref: &connectorv1.AccountRef{ConnectorId: "ledger", AccountId: transitAccountID}, Kind: connectorv1.AccountKind_LEDGER_ACCOUNT, Asset: "USDT", ProviderDetails: detailsTransit},
				},
			}, nil
		},
		TransferInternalFn: func(ctx context.Context, req *ledgerv1.TransferRequest) (*ledgerv1.PostResponse, error) {
			moveCalls++
			return &ledgerv1.PostResponse{JournalEntryRef: "move-1"}, nil
		},
	}

	payoutCalls := 0
	mntxFake := &mntxclient.Fake{
		CreateCardPayoutFn: func(ctx context.Context, req *mntxv1.CardPayoutRequest) (*mntxv1.CardPayoutResponse, error) {
			payoutCalls++
			return &mntxv1.CardPayoutResponse{Payout: &mntxv1.CardPayoutState{PayoutId: "payout-1"}}, nil
		},
	}

	svc := &Service{
		logger:  zap.NewNop(),
		storage: repo,
		deps: serviceDependencies{
			railGateways: buildRailGatewayDependency(map[string]rail.RailGateway{
				"crypto-default": railGateway,
			}, nil, nil, nil, nil),
			ledger: ledgerDependency{
				client:   ledgerFake,
				internal: ledgerFake,
			},
			mntx: mntxDependency{client: mntxFake},
			cardRoutes: map[string]CardGatewayRoute{
				defaultCardGateway: {
					FundingAddress: "funding-address",
					FeeWalletRef:   "fee-wallet",
				},
			},
		},
	}

	executor := newPaymentExecutor(&svc.deps, svc.logger, svc)

	payment := &model.Payment{
		PaymentRef:     "pay-plan-1",
		IdempotencyKey: "pay-plan-1",
		OrganizationBoundBase: mo.OrganizationBoundBase{
			OrganizationRef: bson.NewObjectID(),
		},
		Intent: model.PaymentIntent{
			Ref:  "ref-1",
			Kind: model.PaymentKindPayout,
			Source: model.PaymentEndpoint{
				Type: model.EndpointTypeManagedWallet,
				ManagedWallet: &model.ManagedWalletEndpoint{
					ManagedWalletRef: "wallet-src",
				},
			},
			Destination: model.PaymentEndpoint{
				Type: model.EndpointTypeCard,
				Card: &model.CardEndpoint{
					Pan:               "4111111111111111",
					Cardholder:        "Ada",
					CardholderSurname: "Lovelace",
					ExpMonth:          1,
					ExpYear:           2030,
					MaskedPan:         "4111",
				},
			},
			Attributes: map[string]string{
				"ledger_credit_account_ref": "ledger:credit",
				"ledger_debit_account_ref":  "ledger:debit",
			},
			Customer: &model.Customer{
				ID:        "cust-1",
				FirstName: "Ada",
				LastName:  "Lovelace",
				IP:        "1.2.3.4",
			},
		},
		PaymentPlan: &model.PaymentPlan{
			ID:             "pay-plan-1",
			IdempotencyKey: "pay-plan-1",
			Steps: []*model.PaymentStep{
				{StepID: "crypto_send", Rail: model.RailCrypto, Action: model.RailOperationSend, Amount: &paymenttypes.Money{Currency: "USDT", Amount: "100"}},
				{StepID: "crypto_fee", Rail: model.RailCrypto, Action: model.RailOperationFee, DependsOn: []string{"crypto_send"}, Amount: &paymenttypes.Money{Currency: "USDT", Amount: "5"}},
				{StepID: "crypto_observe", Rail: model.RailProviderSettlement, Action: model.RailOperationObserveConfirm, DependsOn: []string{"crypto_send"}},
				{StepID: "ledger_credit", Rail: model.RailLedger, Action: model.RailOperationMove, DependsOn: []string{"crypto_observe"}, Amount: &paymenttypes.Money{Currency: "USDT", Amount: "95"}, FromRole: rolePtr(account_role.AccountRolePending), ToRole: rolePtr(account_role.AccountRoleOperating)},
				{StepID: "card_payout", Rail: model.RailCardPayout, Action: model.RailOperationSend, DependsOn: []string{"ledger_credit"}, Amount: &paymenttypes.Money{Currency: "USDT", Amount: "95"}},
				{StepID: "ledger_debit", Rail: model.RailLedger, Action: model.RailOperationMove, DependsOn: []string{"card_payout"}, CommitPolicy: model.CommitPolicyAfterSuccess, CommitAfter: []string{"card_payout"}, Amount: &paymenttypes.Money{Currency: "USDT", Amount: "95"}, FromRole: rolePtr(account_role.AccountRoleOperating), ToRole: rolePtr(account_role.AccountRoleTransit)},
			},
		},
	}

	store.payments[payment.PaymentRef] = payment

	if err := executor.executePaymentPlan(ctx, store, payment, &orchestratorv1.PaymentQuote{}); err != nil {
		t.Fatalf("executePaymentPlan error: %v", err)
	}

	if payment.Execution == nil || payment.Execution.ChainTransferRef == "" {
		t.Fatalf("expected chain transfer ref set")
	}
	if payment.Execution.FeeTransferRef != "" {
		t.Fatalf("fee must NOT be executed before send success")
	}

	steps := executionStepsByCode(payment.ExecutionPlan)

	if steps["crypto_send"].State != model.OperationStateWaiting {
		t.Fatalf("send must be waiting")
	}
	if steps["crypto_fee"].State != model.OperationStatePlanned {
		t.Fatalf("fee must NOT start before send success")
	}
	if steps["crypto_observe"].State != model.OperationStatePlanned {
		t.Fatalf("observe must NOT start before send success")
	}

	// ---- имитируем подтверждение сети по crypto_send ----
	setExecutionStepStatus(steps["crypto_send"], model.OperationStateSuccess)

	if err := executor.executePaymentPlan(ctx, store, payment, &orchestratorv1.PaymentQuote{}); err != nil {
		t.Fatalf("executePaymentPlan resume error: %v", err)
	}

	// Теперь должны стартовать fee и observe
	if steps["crypto_fee"].State != model.OperationStateWaiting {
		t.Fatalf("fee must start after send success")
	}
	if steps["crypto_observe"].State != model.OperationStateWaiting {
		t.Fatalf("observe must start after send success")
	}

	// Имитируем подтверждение observe (это unlock ledger_credit)
	setExecutionStepStatus(steps["crypto_observe"], model.OperationStateSuccess)

	if err := executor.executePaymentPlan(ctx, store, payment, &orchestratorv1.PaymentQuote{}); err != nil {
		t.Fatalf("executePaymentPlan resume after observe error: %v", err)
	}

	if moveCalls != 1 {
		t.Fatalf("expected one ledger move after observe confirmation, got %d", moveCalls)
	}
	if payoutCalls != 1 {
		t.Fatalf("expected card payout submitted, got %d", payoutCalls)
	}

	// Mock card payout confirmation
	cardStep := executionStepsByCode(payment.ExecutionPlan)["card_payout"]
	setExecutionStepStatus(cardStep, model.OperationStateSuccess)

	if err := executor.executePaymentPlan(ctx, store, payment, &orchestratorv1.PaymentQuote{}); err != nil {
		t.Fatalf("executePaymentPlan finalize error: %v", err)
	}

	if moveCalls != 2 {
		t.Fatalf("expected two ledger moves after payout confirmation, got %d", moveCalls)
	}

}

func TestExecutePaymentPlan_RejectsLegacyLedgerOperations(t *testing.T) {
	ctx := context.Background()

	store := newStubPaymentsStore()
	repo := &stubRepository{store: store}

	ledgerFake := &ledgerclient.Fake{}

	svc := &Service{
		logger:  zap.NewNop(),
		storage: repo,
		deps: serviceDependencies{
			ledger: ledgerDependency{
				client:   ledgerFake,
				internal: ledgerFake,
			},
		},
	}

	executor := newPaymentExecutor(&svc.deps, svc.logger, svc)

	payment := &model.Payment{
		PaymentRef:     "pay-legacy-1",
		IdempotencyKey: "pay-legacy-1",
		OrganizationBoundBase: mo.OrganizationBoundBase{
			OrganizationRef: bson.NewObjectID(),
		},
		Intent: model.PaymentIntent{
			Ref:  "ref-legacy-1",
			Kind: model.PaymentKindPayout,
			Source: model.PaymentEndpoint{
				Type: model.EndpointTypeManagedWallet,
				ManagedWallet: &model.ManagedWalletEndpoint{
					ManagedWalletRef: "wallet-src",
				},
			},
			Destination: model.PaymentEndpoint{
				Type: model.EndpointTypeCard,
				Card: &model.CardEndpoint{MaskedPan: "4111"},
			},
		},
		PaymentPlan: &model.PaymentPlan{
			ID:             "pay-legacy-1",
			IdempotencyKey: "pay-legacy-1",
			Steps: []*model.PaymentStep{
				{StepID: "ledger_block", Rail: model.RailLedger, Action: model.RailOperationBlock, Amount: &paymenttypes.Money{Currency: "USD", Amount: "100"}},
			},
		},
	}

	store.payments[payment.PaymentRef] = payment

	err := executor.executePaymentPlan(ctx, store, payment, &orchestratorv1.PaymentQuote{})
	if err == nil {
		t.Fatal("expected legacy ledger operation error")
	}
	if !strings.Contains(err.Error(), "unsupported action") {
		t.Fatalf("unexpected error: %v", err)
	}
}
