package orchestrator

import (
	"context"
	"errors"
	"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/prepo"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/psvc"
	"github.com/tech/sendico/payments/storage/model"
	"github.com/tech/sendico/pkg/discovery"
	pm "github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/payments/rail"
	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"
	orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
)

func TestBuildGatewayExecutionEvent_MapsStatusAndMatchedStep(t *testing.T) {
	orgID := bson.NewObjectID()
	payment := &agg.Payment{
		OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
		PaymentRef:            "payment-1",
		StepExecutions: []agg.StepExecution{
			{
				StepRef:  "hop_1_crypto_observe",
				StepCode: "hop.1.crypto.observe",
				State:    agg.StepStateRunning,
				ExternalRefs: []agg.ExternalRef{
					{
						GatewayInstanceID: "crypto_rail_gateway_tron_nile",
						Kind:              erecon.ExternalRefKindTransfer,
						Ref:               "trf-1",
					},
				},
			},
		},
	}

	event, ok := buildGatewayExecutionEvent(payment, &pm.PaymentGatewayExecution{
		PaymentRef:    payment.PaymentRef,
		Status:        rail.OperationResultSuccess,
		TransferRef:   "trf-1",
		ExecutedMoney: &paymenttypes.Money{Amount: "5.84", Currency: "USDT"},
	})
	if !ok {
		t.Fatal("expected gateway execution event to be accepted")
	}
	if got, want := event.StepRef, "hop_1_crypto_observe"; got != want {
		t.Fatalf("step_ref mismatch: got=%q want=%q", got, want)
	}
	if got, want := event.GatewayInstanceID, "crypto_rail_gateway_tron_nile"; got != want {
		t.Fatalf("gateway_instance_id mismatch: got=%q want=%q", got, want)
	}
	if got, want := event.Status, erecon.GatewayStatusSuccess; got != want {
		t.Fatalf("status mismatch: got=%q want=%q", got, want)
	}
	if event.ExecutedMoney == nil {
		t.Fatal("expected executed money to be mapped")
	}
	if got, want := event.ExecutedMoney.Amount, "5.84"; got != want {
		t.Fatalf("executed money amount mismatch: got=%q want=%q", got, want)
	}
	if got, want := event.ExecutedMoney.Currency, "USDT"; got != want {
		t.Fatalf("executed money currency mismatch: got=%q want=%q", got, want)
	}
}

func TestBuildGatewayExecutionEvent_FailedSetsTerminalNeedsAttentionHint(t *testing.T) {
	orgID := bson.NewObjectID()
	payment := &agg.Payment{
		OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
		PaymentRef:            "payment-2",
		StepExecutions: []agg.StepExecution{
			{
				StepRef:  "hop_1_crypto_observe",
				StepCode: "hop.1.crypto.observe",
				State:    agg.StepStateRunning,
				ExternalRefs: []agg.ExternalRef{
					{Kind: erecon.ExternalRefKindTransfer, Ref: "trf-2"},
				},
			},
		},
	}

	event, ok := buildGatewayExecutionEvent(payment, &pm.PaymentGatewayExecution{
		PaymentRef:  payment.PaymentRef,
		Status:      rail.OperationResultFailed,
		TransferRef: "trf-2",
		Error:       "insufficient funds",
	})
	if !ok {
		t.Fatal("expected failed gateway execution event to be accepted")
	}
	if event.Retryable == nil || *event.Retryable {
		t.Fatal("expected retryable=false for failed gateway execution")
	}
	if got, want := event.FailureCode, "gateway.execution_failed"; got != want {
		t.Fatalf("failure_code mismatch: got=%q want=%q", got, want)
	}
	if got, want := event.FailureMsg, "insufficient funds"; got != want {
		t.Fatalf("failure_msg mismatch: got=%q want=%q", got, want)
	}
}

func TestBuildGatewayExecutionEvent_MatchesCardObserveByCardPayoutRef(t *testing.T) {
	orgID := bson.NewObjectID()
	payment := &agg.Payment{
		OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
		PaymentRef:            "payment-card-1",
		StepExecutions: []agg.StepExecution{
			{
				StepRef:  "hop_4_card_payout_observe",
				StepCode: "hop.4.card_payout.observe",
				State:    agg.StepStateRunning,
				ExternalRefs: []agg.ExternalRef{
					{
						GatewayInstanceID: paymenttypes.DefaultCardsGatewayID,
						Kind:              erecon.ExternalRefKindCardPayout,
						Ref:               "payout-1",
					},
				},
			},
		},
	}

	event, ok := buildGatewayExecutionEvent(payment, &pm.PaymentGatewayExecution{
		PaymentRef:  payment.PaymentRef,
		Status:      rail.OperationResultSuccess,
		TransferRef: "payout-1",
	})
	if !ok {
		t.Fatal("expected gateway execution event to be accepted")
	}
	if got, want := event.StepRef, "hop_4_card_payout_observe"; got != want {
		t.Fatalf("step_ref mismatch: got=%q want=%q", got, want)
	}
	if got, want := event.GatewayInstanceID, paymenttypes.DefaultCardsGatewayID; got != want {
		t.Fatalf("gateway_instance_id mismatch: got=%q want=%q", got, want)
	}
}

func TestBuildGatewayExecutionEvent_SkipsUnmatchedRefsEvenWithSingleRunningObserve(t *testing.T) {
	orgID := bson.NewObjectID()
	payment := &agg.Payment{
		OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
		PaymentRef:            "payment-settlement-1",
		StepExecutions: []agg.StepExecution{
			{
				StepRef:  "hop_2_settlement_observe",
				StepCode: "hop.2.settlement.observe",
				State:    agg.StepStateRunning,
				ExternalRefs: []agg.ExternalRef{
					{
						GatewayInstanceID: "payment_gateway_settlement",
						Kind:              erecon.ExternalRefKindTransfer,
						Ref:               "settlement-transfer-ref",
					},
				},
			},
		},
	}

	// This models a foreign success event (e.g. crypto fee transfer) that should
	// never close settlement observe by "single running observe" fallback.
	_, ok := buildGatewayExecutionEvent(payment, &pm.PaymentGatewayExecution{
		PaymentRef:   payment.PaymentRef,
		Status:       rail.OperationResultSuccess,
		OperationRef: "payment-1:hop_1_crypto_send:fee",
		TransferRef:  "fee-transfer-ref",
	})
	if ok {
		t.Fatal("expected unmatched gateway execution event to be skipped")
	}
}

func TestBuildGatewayExecutionEvent_AllowsSingleRunningObserveFallbackWhenRefsMissing(t *testing.T) {
	orgID := bson.NewObjectID()
	payment := &agg.Payment{
		OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
		PaymentRef:            "payment-settlement-2",
		StepExecutions: []agg.StepExecution{
			{
				StepRef:  "hop_2_settlement_observe",
				StepCode: "hop.2.settlement.observe",
				State:    agg.StepStateRunning,
				ExternalRefs: []agg.ExternalRef{
					{
						GatewayInstanceID: "payment_gateway_settlement",
						Kind:              erecon.ExternalRefKindTransfer,
						Ref:               "settlement-transfer-ref",
					},
				},
			},
		},
	}

	event, ok := buildGatewayExecutionEvent(payment, &pm.PaymentGatewayExecution{
		PaymentRef: payment.PaymentRef,
		Status:     rail.OperationResultSuccess,
	})
	if !ok {
		t.Fatal("expected gateway execution event to be accepted")
	}
	if got, want := event.StepRef, "hop_2_settlement_observe"; got != want {
		t.Fatalf("step_ref mismatch: got=%q want=%q", got, want)
	}
}

func TestOnPaymentGatewayExecution_ReconcilesUsingGlobalPaymentLookup(t *testing.T) {
	orgID := bson.NewObjectID()
	payment := &agg.Payment{
		OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
		PaymentRef:            "payment-3",
		StepExecutions: []agg.StepExecution{
			{
				StepRef:  "hop_1_crypto_observe",
				StepCode: "hop.1.crypto.observe",
				State:    agg.StepStateRunning,
				ExternalRefs: []agg.ExternalRef{
					{Kind: erecon.ExternalRefKindTransfer, Ref: "trf-3"},
				},
			},
		},
	}

	repo := &fakeExternalRuntimeRepo{payment: payment}
	v2 := &fakeExternalRuntimeV2{}
	svc := &Service{
		v2:          v2,
		paymentRepo: repo,
		logger:      zap.NewNop(),
	}

	err := svc.onPaymentGatewayExecution(context.Background(), &pm.PaymentGatewayExecution{
		PaymentRef:  payment.PaymentRef,
		Status:      rail.OperationResultSuccess,
		TransferRef: "trf-3",
	})
	if err != nil {
		t.Fatalf("onPaymentGatewayExecution returned error: %v", err)
	}
	if v2.reconcileInput == nil {
		t.Fatal("expected reconcile call")
	}
	if got, want := v2.reconcileInput.OrganizationRef, orgID.Hex(); got != want {
		t.Fatalf("organization_ref mismatch: got=%q want=%q", got, want)
	}
	if got, want := v2.reconcileInput.PaymentRef, payment.PaymentRef; got != want {
		t.Fatalf("payment_ref mismatch: got=%q want=%q", got, want)
	}
	if v2.reconcileInput.Event.Gateway == nil || v2.reconcileInput.Event.Gateway.Status != erecon.GatewayStatusSuccess {
		t.Fatal("expected success gateway reconcile event")
	}
}

type fakeExternalRuntimeRepo struct {
	payment *agg.Payment
	err     error
}

func (f *fakeExternalRuntimeRepo) Create(context.Context, *agg.Payment) error { return nil }

func (f *fakeExternalRuntimeRepo) UpdateCAS(context.Context, *agg.Payment, uint64) error { return nil }

func (f *fakeExternalRuntimeRepo) GetByPaymentRef(_ context.Context, _ bson.ObjectID, _ string) (*agg.Payment, error) {
	return nil, prepo.ErrPaymentNotFound
}

func (f *fakeExternalRuntimeRepo) GetByPaymentRefGlobal(_ context.Context, paymentRef string) (*agg.Payment, error) {
	if f.err != nil {
		return nil, f.err
	}
	if f.payment == nil || f.payment.PaymentRef != paymentRef {
		return nil, prepo.ErrPaymentNotFound
	}
	return f.payment, nil
}

func (f *fakeExternalRuntimeRepo) GetByIdempotencyKey(context.Context, bson.ObjectID, string) (*agg.Payment, error) {
	return nil, prepo.ErrPaymentNotFound
}

func (f *fakeExternalRuntimeRepo) ListByQuotationRef(context.Context, prepo.ListByQuotationRefInput) (*prepo.ListOutput, error) {
	return &prepo.ListOutput{}, nil
}

func (f *fakeExternalRuntimeRepo) ListByState(context.Context, prepo.ListByStateInput) (*prepo.ListOutput, error) {
	return &prepo.ListOutput{}, nil
}

func (f *fakeExternalRuntimeRepo) ListByStateGlobal(context.Context, prepo.ListByStateGlobalInput) (*prepo.ListOutput, error) {
	return &prepo.ListOutput{}, nil
}

type fakeExternalRuntimeV2 struct {
	reconcileInput *psvc.ReconcileExternalInput
}

func (f *fakeExternalRuntimeV2) ExecutePayment(context.Context, *orchestrationv2.ExecutePaymentRequest) (*orchestrationv2.ExecutePaymentResponse, error) {
	return nil, errors.New("not implemented")
}

func (f *fakeExternalRuntimeV2) GetPayment(context.Context, *orchestrationv2.GetPaymentRequest) (*orchestrationv2.GetPaymentResponse, error) {
	return nil, errors.New("not implemented")
}

func (f *fakeExternalRuntimeV2) ListPayments(context.Context, *orchestrationv2.ListPaymentsRequest) (*orchestrationv2.ListPaymentsResponse, error) {
	return nil, errors.New("not implemented")
}

func (f *fakeExternalRuntimeV2) ReconcileExternal(_ context.Context, in psvc.ReconcileExternalInput) (*psvc.ReconcileExternalOutput, error) {
	cloned := in
	f.reconcileInput = &cloned
	return &psvc.ReconcileExternalOutput{
		Payment: &orchestrationv2.Payment{
			PaymentRef: in.PaymentRef,
			State:      orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_EXECUTING,
			Version:    1,
		},
	}, nil
}

func (f *fakeExternalRuntimeV2) ExecuteBatchPayment(ctx context.Context, req *orchestrationv2.ExecuteBatchPaymentRequest) (*orchestrationv2.ExecuteBatchPaymentResponse, error) {
	return nil, errors.New("not implemented")
}

func TestMapTransferStatus(t *testing.T) {
	cases := []struct {
		status        chainv1.TransferStatus
		wantStatus    erecon.GatewayStatus
		wantTerminal  bool
		wantSupported bool
	}{
		{status: chainv1.TransferStatus_TRANSFER_CREATED, wantStatus: erecon.GatewayStatusCreated, wantTerminal: false, wantSupported: true},
		{status: chainv1.TransferStatus_TRANSFER_PROCESSING, wantStatus: erecon.GatewayStatusProcessing, wantTerminal: false, wantSupported: true},
		{status: chainv1.TransferStatus_TRANSFER_WAITING, wantStatus: erecon.GatewayStatusWaiting, wantTerminal: false, wantSupported: true},
		{status: chainv1.TransferStatus_TRANSFER_SUCCESS, wantStatus: erecon.GatewayStatusSuccess, wantTerminal: true, wantSupported: true},
		{status: chainv1.TransferStatus_TRANSFER_FAILED, wantStatus: erecon.GatewayStatusFailed, wantTerminal: true, wantSupported: true},
		{status: chainv1.TransferStatus_TRANSFER_CANCELLED, wantStatus: erecon.GatewayStatusCancelled, wantTerminal: true, wantSupported: true},
	}

	for _, tc := range cases {
		gotStatus, gotTerminal, gotSupported := mapTransferStatus(tc.status)
		if gotStatus != tc.wantStatus || gotTerminal != tc.wantTerminal || gotSupported != tc.wantSupported {
			t.Fatalf("status mapping mismatch: status=%v got=(%q,%v,%v) want=(%q,%v,%v)",
				tc.status, gotStatus, gotTerminal, gotSupported, tc.wantStatus, tc.wantTerminal, tc.wantSupported)
		}
	}
}

func TestRunningObserveCandidates(t *testing.T) {
	payment := &agg.Payment{
		StepExecutions: []agg.StepExecution{
			{
				StepRef:  "hop_1_crypto_observe",
				StepCode: "hop.1.crypto.observe",
				State:    agg.StepStateRunning,
				ExternalRefs: []agg.ExternalRef{
					{Kind: erecon.ExternalRefKindTransfer, Ref: "trf-running"},
				},
			},
			{
				StepRef:  "hop_2_crypto_observe",
				StepCode: "hop.2.crypto.observe",
				State:    agg.StepStateCompleted,
				ExternalRefs: []agg.ExternalRef{
					{Kind: erecon.ExternalRefKindTransfer, Ref: "trf-completed"},
				},
			},
			{
				StepRef:  "hop_3_crypto_observe",
				StepCode: "hop.3.crypto.observe",
				State:    agg.StepStateRunning,
				ExternalRefs: []agg.ExternalRef{
					{Kind: erecon.ExternalRefKindOperation, Ref: "op-only"},
				},
			},
		},
	}

	candidates := runningObserveCandidates(payment)
	if len(candidates) != 1 {
		t.Fatalf("candidate count mismatch: got=%d want=1", len(candidates))
	}
	if got, want := candidates[0].transferRef, "trf-running"; got != want {
		t.Fatalf("transfer_ref mismatch: got=%q want=%q", got, want)
	}
}

func TestRunningObserveCandidates_UsesCardPayoutRefAsTransfer(t *testing.T) {
	payment := &agg.Payment{
		StepExecutions: []agg.StepExecution{
			{
				StepRef:  "hop_4_card_payout_observe",
				StepCode: "hop.4.card_payout.observe",
				State:    agg.StepStateRunning,
				ExternalRefs: []agg.ExternalRef{
					{
						GatewayInstanceID: paymenttypes.DefaultCardsGatewayID,
						Kind:              erecon.ExternalRefKindCardPayout,
						Ref:               "payout-2",
					},
				},
			},
		},
	}

	candidates := runningObserveCandidates(payment)
	if len(candidates) != 1 {
		t.Fatalf("candidate count mismatch: got=%d want=1", len(candidates))
	}
	if got, want := candidates[0].transferRef, "payout-2"; got != want {
		t.Fatalf("transfer_ref mismatch: got=%q want=%q", got, want)
	}
	if got, want := candidates[0].gatewayInstanceID, paymenttypes.DefaultCardsGatewayID; got != want {
		t.Fatalf("gateway_instance_id mismatch: got=%q want=%q", got, want)
	}
}

func TestRunningObserveCandidates_UsesPlannedStepInstanceWhenExternalRefGatewayMissing(t *testing.T) {
	payment := &agg.Payment{
		StepExecutions: []agg.StepExecution{
			{
				StepRef:    "hop_2_settlement_observe",
				StepCode:   "hop.2.settlement.observe",
				InstanceID: "04a54fec-20f4-4250-a715-eb9886e13e12",
				State:      agg.StepStateRunning,
				ExternalRefs: []agg.ExternalRef{
					{Kind: erecon.ExternalRefKindTransfer, Ref: "trf-2"},
				},
			},
		},
	}

	candidates := runningObserveCandidates(payment)
	if len(candidates) != 1 {
		t.Fatalf("candidate count mismatch: got=%d want=1", len(candidates))
	}
	if got, want := candidates[0].gatewayInstanceID, "04a54fec-20f4-4250-a715-eb9886e13e12"; got != want {
		t.Fatalf("gateway_instance_id mismatch: got=%q want=%q", got, want)
	}
}

func TestResolveObserveGateway_UsesExternalRefGatewayInstanceAcrossRails(t *testing.T) {
	svc := &Service{
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "payment_gateway_settlement",
					InstanceID: "payment_gateway_settlement",
					Rail:       discovery.RailProviderSettlement,
					InvokeURI:  "grpc://tgsettle-gateway",
					IsEnabled:  true,
				},
				{
					ID:         "crypto_rail_gateway_tron_nile",
					InstanceID: "crypto_rail_gateway_tron_nile",
					Rail:       discovery.RailCrypto,
					InvokeURI:  "grpc://tron-gateway",
					IsEnabled:  true,
				},
			},
		},
	}

	payment := &agg.Payment{
		QuoteSnapshot: &model.PaymentQuoteSnapshot{
			Route: &paymenttypes.QuoteRouteSpecification{
				Hops: []*paymenttypes.QuoteRouteHop{
					{
						Index:      1,
						Rail:       "CRYPTO",
						Gateway:    "crypto_rail_gateway_tron_nile",
						InstanceID: "crypto_rail_gateway_tron_nile",
						Role:       paymenttypes.QuoteRouteHopRoleSource,
					},
				},
			},
		},
	}

	gateway, err := svc.resolveObserveGateway(context.Background(), payment, runningObserveCandidate{
		stepRef:           "hop_2_settlement_observe",
		transferRef:       "trf-1",
		gatewayInstanceID: "payment_gateway_settlement",
	})
	if err != nil {
		t.Fatalf("resolveObserveGateway returned error: %v", err)
	}
	if gateway == nil {
		t.Fatal("expected gateway")
	}
	if got, want := gateway.ID, "payment_gateway_settlement"; got != want {
		t.Fatalf("gateway id mismatch: got=%q want=%q", got, want)
	}
}

func TestResolveObserveGateway_UsesPlannedStepGatewayWhenExternalRefInstanceIsStale(t *testing.T) {
	svc := &Service{
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "payment_gateway_settlement",
					InstanceID: "ea2600ce-3de6-4cc5-bd1e-e26ebaceb6b4",
					Rail:       discovery.RailProviderSettlement,
					InvokeURI:  "grpc://tgsettle-gateway-new",
					IsEnabled:  true,
				},
				{
					ID:         "crypto_rail_gateway_tron_mainnet",
					InstanceID: "fbef2c3b-ff66-447e-8bba-fa666a955855",
					Rail:       discovery.RailCrypto,
					InvokeURI:  "grpc://tron-gateway",
					IsEnabled:  true,
				},
			},
		},
	}

	payment := &agg.Payment{
		StepExecutions: []agg.StepExecution{
			{
				StepRef:    "hop_2_settlement_observe",
				StepCode:   "hop.2.settlement.observe",
				Rail:       discovery.RailProviderSettlement,
				Gateway:    "payment_gateway_settlement",
				InstanceID: "04a54fec-20f4-4250-a715-eb9886e13e12",
			},
		},
	}

	gateway, err := svc.resolveObserveGateway(context.Background(), payment, runningObserveCandidate{
		stepRef:           "hop_2_settlement_observe",
		transferRef:       "trf-1",
		gatewayInstanceID: "04a54fec-20f4-4250-a715-eb9886e13e12",
	})
	if err != nil {
		t.Fatalf("resolveObserveGateway returned error: %v", err)
	}
	if gateway == nil {
		t.Fatal("expected gateway")
	}
	if got, want := gateway.ID, "payment_gateway_settlement"; got != want {
		t.Fatalf("gateway id mismatch: got=%q want=%q", got, want)
	}
	if got, want := gateway.InstanceID, "ea2600ce-3de6-4cc5-bd1e-e26ebaceb6b4"; got != want {
		t.Fatalf("gateway instance mismatch: got=%q want=%q", got, want)
	}
}

func TestResolveObserveGateway_FailsWhenPlannedGatewayMetadataIsMissing(t *testing.T) {
	svc := &Service{
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "crypto_rail_gateway_tron_mainnet",
					InstanceID: "fbef2c3b-ff66-447e-8bba-fa666a955855",
					Rail:       discovery.RailCrypto,
					InvokeURI:  "grpc://tron-gateway",
					IsEnabled:  true,
				},
			},
		},
	}

	payment := &agg.Payment{
		QuoteSnapshot: &model.PaymentQuoteSnapshot{
			Route: &paymenttypes.QuoteRouteSpecification{
				Hops: []*paymenttypes.QuoteRouteHop{
					{
						Index:      1,
						Rail:       "CRYPTO",
						Gateway:    "crypto_rail_gateway_tron_mainnet",
						InstanceID: "fbef2c3b-ff66-447e-8bba-fa666a955855",
						Role:       paymenttypes.QuoteRouteHopRoleSource,
					},
				},
			},
		},
	}

	gateway, err := svc.resolveObserveGateway(context.Background(), payment, runningObserveCandidate{
		stepRef:           "hop_2_settlement_observe",
		transferRef:       "trf-1",
		gatewayInstanceID: "04a54fec-20f4-4250-a715-eb9886e13e12",
	})
	if err == nil {
		t.Fatal("expected gateway resolution error")
	}
	if gateway != nil {
		t.Fatal("expected nil gateway on resolution failure")
	}
}

func TestPollObserveCandidate_UsesResolvedGatewayAfterInstanceRotation(t *testing.T) {
	orgID := bson.NewObjectID()
	transferRef := "b6874b55-20b0-425d-9e47-d430964b1616:hop_2_settlement_fx_convert"
	operationRef := "69aabf823555e083d23b2964:hop_2_settlement_fx_convert"

	var requestedTransferRef string
	client := &chainclient.Fake{
		GetTransferFn: func(_ context.Context, req *chainv1.GetTransferRequest) (*chainv1.GetTransferResponse, error) {
			requestedTransferRef = req.GetTransferRef()
			return &chainv1.GetTransferResponse{
				Transfer: &chainv1.Transfer{
					TransferRef:  req.GetTransferRef(),
					OperationRef: operationRef,
					Status:       chainv1.TransferStatus_TRANSFER_SUCCESS,
					NetAmount:    &moneyv1.Money{Amount: "460.00", Currency: "RUB"},
				},
			}, nil
		},
	}
	resolver := &fakeGatewayInvokeResolver{client: client}
	v2 := &fakeExternalRuntimeV2{}
	svc := &Service{
		logger:                zap.NewNop(),
		v2:                    v2,
		gatewayInvokeResolver: resolver,
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "payment_gateway_settlement",
					InstanceID: "ea2600ce-3de6-4cc5-bd1e-e26ebaceb6b4",
					Rail:       discovery.RailProviderSettlement,
					InvokeURI:  "grpc://tgsettle-gateway-new",
					IsEnabled:  true,
				},
			},
		},
	}

	payment := &agg.Payment{
		OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
		PaymentRef:            "69aabf823555e083d23b2964",
		StepExecutions: []agg.StepExecution{
			{
				StepRef:    "hop_2_settlement_observe",
				StepCode:   "hop.2.settlement.observe",
				Rail:       discovery.RailProviderSettlement,
				Gateway:    "payment_gateway_settlement",
				InstanceID: "04a54fec-20f4-4250-a715-eb9886e13e12",
				State:      agg.StepStateRunning,
				ExternalRefs: []agg.ExternalRef{
					{
						GatewayInstanceID: "04a54fec-20f4-4250-a715-eb9886e13e12",
						Kind:              erecon.ExternalRefKindTransfer,
						Ref:               transferRef,
					},
				},
			},
		},
	}

	candidates := runningObserveCandidates(payment)
	if len(candidates) != 1 {
		t.Fatalf("candidate count mismatch: got=%d want=1", len(candidates))
	}

	svc.pollObserveCandidate(context.Background(), payment, candidates[0])

	if got, want := resolver.lastInvokeURI, "grpc://tgsettle-gateway-new"; got != want {
		t.Fatalf("invoke uri mismatch: got=%q want=%q", got, want)
	}
	if got, want := requestedTransferRef, transferRef; got != want {
		t.Fatalf("transfer_ref lookup mismatch: got=%q want=%q", got, want)
	}
	if v2.reconcileInput == nil || v2.reconcileInput.Event.Gateway == nil {
		t.Fatal("expected reconcile gateway event")
	}
	gw := v2.reconcileInput.Event.Gateway
	if got, want := gw.StepRef, "hop_2_settlement_observe"; got != want {
		t.Fatalf("step_ref mismatch: got=%q want=%q", got, want)
	}
	if got, want := gw.Status, erecon.GatewayStatusSuccess; got != want {
		t.Fatalf("status mismatch: got=%q want=%q", got, want)
	}
	if got, want := gw.OperationRef, operationRef; got != want {
		t.Fatalf("operation_ref mismatch: got=%q want=%q", got, want)
	}
	if got, want := gw.GatewayInstanceID, "ea2600ce-3de6-4cc5-bd1e-e26ebaceb6b4"; got != want {
		t.Fatalf("gateway_instance_id mismatch: got=%q want=%q", got, want)
	}
	if gw.ExecutedMoney == nil {
		t.Fatal("expected executed money in reconciled gateway event")
	}
	if got, want := gw.ExecutedMoney.Amount, "460.00"; got != want {
		t.Fatalf("executed money amount mismatch: got=%q want=%q", got, want)
	}
	if got, want := gw.ExecutedMoney.Currency, "RUB"; got != want {
		t.Fatalf("executed money currency mismatch: got=%q want=%q", got, want)
	}
}

var _ prepo.Repository = (*fakeExternalRuntimeRepo)(nil)
var _ psvc.Service = (*fakeExternalRuntimeV2)(nil)
