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/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 TestGatewayProviderSettlementExecutor_ExecuteProviderSettlement_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-settlement-1",
					OperationRef: "op-settlement-1",
				},
			}, nil
		},
	}
	resolver := &fakeGatewayInvokeResolver{client: client}
	registry := &fakeGatewayRegistry{
		items: []*model.GatewayInstanceDescriptor{
			{
				ID:         "payment_gateway_settlement",
				InstanceID: "payment_gateway_settlement",
				Rail:       discovery.RailProviderSettlement,
				InvokeURI:  "grpc://tgsettle-gateway",
				IsEnabled:  true,
			},
		},
	}
	executor := &gatewayProviderSettlementExecutor{
		gatewayInvokeResolver: resolver,
		gatewayRegistry:       registry,
	}

	req := sexec.StepRequest{
		Payment: &agg.Payment{
			OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
			PaymentRef:            "payment-1",
			IdempotencyKey:        "idem-1",
			QuotationRef:          "quote-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"},
				ExpectedSettlementAmount: &paymenttypes.Money{Amount: "76.63", Currency: "RUB"},
				QuoteRef:                 "quote-1",
			},
		},
		Step: xplan.Step{
			StepRef:    "hop_2_settlement_fx_convert",
			StepCode:   "hop.2.settlement.fx_convert",
			Action:     discovery.RailOperationFXConvert,
			Rail:       discovery.RailProviderSettlement,
			Gateway:    "payment_gateway_settlement",
			InstanceID: "payment_gateway_settlement",
		},
		StepExecution: agg.StepExecution{
			StepRef:  "hop_2_settlement_fx_convert",
			StepCode: "hop.2.settlement.fx_convert",
			Attempt:  1,
		},
	}

	out, err := executor.ExecuteProviderSettlement(context.Background(), req)
	if err != nil {
		t.Fatalf("ExecuteProviderSettlement 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 out.StepExecution.ConvertedMoney == nil {
		t.Fatal("expected converted money to be recorded for settlement fx_convert")
	}
	if got, want := out.StepExecution.ConvertedMoney.Amount, "76.63"; got != want {
		t.Fatalf("converted money amount mismatch: got=%q want=%q", got, want)
	}
	if got, want := out.StepExecution.ConvertedMoney.Currency, "RUB"; got != want {
		t.Fatalf("converted money currency mismatch: got=%q want=%q", got, want)
	}
	if submitReq == nil {
		t.Fatal("expected transfer submission request")
	}
	if got, want := resolver.lastInvokeURI, "grpc://tgsettle-gateway"; got != want {
		t.Fatalf("invoke uri mismatch: got=%q want=%q", got, want)
	}
	if got, want := submitReq.GetSourceWalletRef(), "wallet-src"; got != want {
		t.Fatalf("source wallet mismatch: got=%q want=%q", got, want)
	}
	if got, want := submitReq.GetDestination().GetManagedWalletRef(), "wallet-src"; got != want {
		t.Fatalf("destination 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.GetAmount().GetCurrency(), "USDT"; got != want {
		t.Fatalf("currency mismatch: got=%q want=%q", got, want)
	}
	if got, want := submitReq.GetMetadata()[settlementMetadataQuoteRef], "quote-1"; got != want {
		t.Fatalf("quote_ref metadata mismatch: got=%q want=%q", got, want)
	}
	if got, want := submitReq.GetMetadata()[settlementMetadataOutgoingLeg], string(discovery.RailProviderSettlement); got != want {
		t.Fatalf("outgoing_leg metadata 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 TestGatewayProviderSettlementExecutor_ExecuteProviderSettlement_UsesPlannedMoney(t *testing.T) {
	orgID := bson.NewObjectID()

	var submitReq *chainv1.SubmitTransferRequest
	executor := &gatewayProviderSettlementExecutor{
		gatewayInvokeResolver: &fakeGatewayInvokeResolver{
			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
				},
			},
		},
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "payment_gateway_settlement",
					InstanceID: "payment_gateway_settlement",
					Rail:       discovery.RailProviderSettlement,
					InvokeURI:  "grpc://tgsettle-gateway",
					IsEnabled:  true,
				},
			},
		},
	}

	out, err := executor.ExecuteProviderSettlement(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",
					},
				},
				Amount: &paymenttypes.Money{Amount: "1.000000", Currency: "USDT"},
			},
			QuoteSnapshot: &model.PaymentQuoteSnapshot{
				DebitAmount:              &paymenttypes.Money{Amount: "1.000000", Currency: "USDT"},
				ExpectedSettlementAmount: &paymenttypes.Money{Amount: "76.63", Currency: "RUB"},
			},
		},
		Step: xplan.Step{
			StepRef:    "hop_2_settlement_fx_convert",
			StepCode:   "hop.2.settlement.fx_convert",
			Action:     discovery.RailOperationFXConvert,
			Rail:       discovery.RailProviderSettlement,
			Gateway:    "payment_gateway_settlement",
			InstanceID: "payment_gateway_settlement",
		},
		StepExecution: agg.StepExecution{
			StepRef:               "hop_2_settlement_fx_convert",
			StepCode:              "hop.2.settlement.fx_convert",
			Attempt:               1,
			PlannedMoney:          &paymenttypes.Money{Amount: "2.00", Currency: "USDT"},
			PlannedConvertedMoney: &paymenttypes.Money{Amount: "150.00", Currency: "RUB"},
		},
	})
	if err != nil {
		t.Fatalf("ExecuteProviderSettlement returned error: %v", err)
	}
	if submitReq == nil {
		t.Fatal("expected transfer submission request")
	}
	if got, want := submitReq.GetAmount().GetAmount(), "2.00"; 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)
	}
	if out == nil || out.StepExecution.ConvertedMoney == nil {
		t.Fatal("expected converted money in executor output")
	}
	if got, want := out.StepExecution.ConvertedMoney.Amount, "150.00"; got != want {
		t.Fatalf("converted amount mismatch: got=%q want=%q", got, want)
	}
	if got, want := out.StepExecution.ConvertedMoney.Currency, "RUB"; got != want {
		t.Fatalf("converted currency mismatch: got=%q want=%q", got, want)
	}
}

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

	var submitReq *chainv1.SubmitTransferRequest
	executor := &gatewayProviderSettlementExecutor{
		gatewayInvokeResolver: &fakeGatewayInvokeResolver{
			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
				},
			},
		},
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "payment_gateway_settlement",
					InstanceID: "payment_gateway_settlement",
					Rail:       discovery.RailProviderSettlement,
					InvokeURI:  "grpc://tgsettle-gateway",
					IsEnabled:  true,
				},
			},
		},
	}

	out, err := executor.ExecuteProviderSettlement(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: "settlement-note-1",
				Source: model.PaymentEndpoint{
					Type: model.EndpointTypeManagedWallet,
					ManagedWallet: &model.ManagedWalletEndpoint{
						ManagedWalletRef: "wallet-src",
					},
				},
				Amount: &paymenttypes.Money{Amount: "1.000000", Currency: "USDT"},
			},
			QuoteSnapshot: &model.PaymentQuoteSnapshot{
				DebitAmount:              &paymenttypes.Money{Amount: "1.000000", Currency: "USDT"},
				ExpectedSettlementAmount: &paymenttypes.Money{Amount: "76.63", Currency: "RUB"},
			},
		},
		Step: xplan.Step{
			StepRef:    "hop_2_settlement_fx_convert",
			StepCode:   "hop.2.settlement.fx_convert",
			Action:     discovery.RailOperationFXConvert,
			Rail:       discovery.RailProviderSettlement,
			Gateway:    "payment_gateway_settlement",
			InstanceID: "payment_gateway_settlement",
			HopRole:    paymenttypes.QuoteRouteHopRoleDestination,
		},
		StepExecution: agg.StepExecution{
			StepRef:  "hop_2_settlement_fx_convert",
			StepCode: "hop.2.settlement.fx_convert",
			Attempt:  1,
		},
	})
	if err != nil {
		t.Fatalf("ExecuteProviderSettlement 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, "settlement-note-1"; got != want {
		t.Fatalf("step comment mismatch: got=%q want=%q", got, want)
	}
}

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

	executor := &gatewayProviderSettlementExecutor{
		gatewayInvokeResolver: &fakeGatewayInvokeResolver{
			client: &chainclient.Fake{},
		},
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "payment_gateway_settlement",
					InstanceID: "payment_gateway_settlement",
					Rail:       discovery.RailProviderSettlement,
					InvokeURI:  "grpc://tgsettle-gateway",
					IsEnabled:  true,
				},
			},
		},
	}

	_, err := executor.ExecuteProviderSettlement(context.Background(), sexec.StepRequest{
		Payment: &agg.Payment{
			OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
			PaymentRef:            "payment-2",
			IdempotencyKey:        "idem-2",
			IntentSnapshot: model.PaymentIntent{
				Ref: "intent-2",
				Source: model.PaymentEndpoint{
					Type: model.EndpointTypeManagedWallet,
					ManagedWallet: &model.ManagedWalletEndpoint{
						ManagedWalletRef: "wallet-src",
					},
				},
			},
			QuoteSnapshot: nil,
		},
		Step: xplan.Step{
			StepRef:    "hop_2_settlement_fx_convert",
			StepCode:   "hop.2.settlement.fx_convert",
			Action:     discovery.RailOperationFXConvert,
			Rail:       discovery.RailProviderSettlement,
			Gateway:    "payment_gateway_settlement",
			InstanceID: "payment_gateway_settlement",
		},
		StepExecution: agg.StepExecution{
			StepRef:  "hop_2_settlement_fx_convert",
			StepCode: "hop.2.settlement.fx_convert",
			Attempt:  1,
		},
	})
	if err == nil {
		t.Fatal("expected error")
	}
	if !strings.Contains(err.Error(), "debit amount is required") {
		t.Fatalf("unexpected error: %v", err)
	}
}

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

	executor := &gatewayProviderSettlementExecutor{
		gatewayInvokeResolver: &fakeGatewayInvokeResolver{
			client: &chainclient.Fake{},
		},
		gatewayRegistry: &fakeGatewayRegistry{
			items: []*model.GatewayInstanceDescriptor{
				{
					ID:         "payment_gateway_settlement",
					InstanceID: "payment_gateway_settlement",
					Rail:       discovery.RailProviderSettlement,
					InvokeURI:  "grpc://tgsettle-gateway",
					IsEnabled:  true,
				},
			},
		},
	}

	_, err := executor.ExecuteProviderSettlement(context.Background(), sexec.StepRequest{
		Payment: &agg.Payment{
			OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
			PaymentRef:            "payment-3",
			IdempotencyKey:        "idem-3",
			IntentSnapshot: model.PaymentIntent{
				Ref: "intent-3",
				Source: model.PaymentEndpoint{
					Type: model.EndpointTypeManagedWallet,
					ManagedWallet: &model.ManagedWalletEndpoint{
						ManagedWalletRef: "wallet-src",
					},
				},
				Amount: &paymenttypes.Money{Amount: "1.000000", Currency: "USDT"},
			},
			QuoteSnapshot: &model.PaymentQuoteSnapshot{
				DebitAmount: &paymenttypes.Money{Amount: "1.000000", Currency: "USDT"},
			},
		},
		Step: xplan.Step{
			StepRef:    "hop_2_settlement_fx_convert",
			StepCode:   "hop.2.settlement.fx_convert",
			Action:     discovery.RailOperationFXConvert,
			Rail:       discovery.RailProviderSettlement,
			Gateway:    "payment_gateway_settlement",
			InstanceID: "payment_gateway_settlement",
		},
		StepExecution: agg.StepExecution{
			StepRef:  "hop_2_settlement_fx_convert",
			StepCode: "hop.2.settlement.fx_convert",
			Attempt:  1,
		},
	})
	if err == nil {
		t.Fatal("expected error")
	}
	if !strings.Contains(err.Error(), "converted amount is required") {
		t.Fatalf("unexpected error: %v", err)
	}
}
