package psvc

import (
	"context"
	"testing"

	"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"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
)

func TestDefaultGuardExecutor_LiquidityReadyCompletes(t *testing.T) {
	exec := defaultGuardExecutor{}
	out, err := exec.ExecuteGuard(context.Background(), sexec.StepRequest{
		Payment: &agg.Payment{
			QuoteSnapshot: &model.PaymentQuoteSnapshot{
				ExecutionConditions: &paymenttypes.QuoteExecutionConditions{
					Readiness:                         paymenttypes.QuoteExecutionReadinessLiquidityReady,
					LiquidityCheckRequiredAtExecution: true,
				},
			},
		},
		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)
	}
}

func TestDefaultGuardExecutor_LiquidityObtainableFails(t *testing.T) {
	exec := defaultGuardExecutor{}
	out, err := exec.ExecuteGuard(context.Background(), sexec.StepRequest{
		Payment: &agg.Payment{
			QuoteSnapshot: &model.PaymentQuoteSnapshot{
				ExecutionConditions: &paymenttypes.QuoteExecutionConditions{
					Readiness:                         paymenttypes.QuoteExecutionReadinessLiquidityObtainable,
					LiquidityCheckRequiredAtExecution: true,
				},
			},
		},
		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_not_ready"; got != want {
		t.Fatalf("failure code mismatch: got=%q want=%q", got, want)
	}
}

func TestDefaultGuardExecutor_UnknownGuardFails(t *testing.T) {
	exec := defaultGuardExecutor{}
	out, err := exec.ExecuteGuard(context.Background(), sexec.StepRequest{
		Payment: &agg.Payment{
			QuoteSnapshot: &model.PaymentQuoteSnapshot{
				ExecutionConditions: &paymenttypes.QuoteExecutionConditions{
					Readiness: paymenttypes.QuoteExecutionReadinessLiquidityReady,
				},
			},
		},
		Step: xplan.Step{
			StepRef:  "guard_unknown",
			StepCode: "guard.custom",
			Kind:     xplan.StepKindUnspecified,
		},
		StepExecution: agg.StepExecution{
			StepRef:  "guard_unknown",
			StepCode: "guard.custom",
			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.unsupported_step"; got != want {
		t.Fatalf("failure code mismatch: got=%q want=%q", got, want)
	}
}

func TestDefaultObserveConfirmExecutor_InheritsDependencyRefs(t *testing.T) {
	exec := defaultObserveConfirmExecutor{}
	out, err := exec.ExecuteObserveConfirm(context.Background(), sexec.StepRequest{
		Payment: &agg.Payment{
			StepExecutions: []agg.StepExecution{
				{
					StepRef: "hop_1_crypto_send",
					ExecutedMoney: &paymenttypes.Money{
						Amount:   "1.000000",
						Currency: "USDT",
					},
					ConvertedMoney: &paymenttypes.Money{
						Amount:   "95.00",
						Currency: "EUR",
					},
					ExternalRefs: []agg.ExternalRef{
						{GatewayInstanceID: "crypto-gw", Kind: "operation_ref", Ref: "op-1"},
						{GatewayInstanceID: "crypto-gw", Kind: "transfer_ref", Ref: "trf-1"},
					},
				},
			},
		},
		Step: xplan.Step{
			StepRef:   "hop_1_crypto_observe",
			StepCode:  "hop.1.crypto.observe",
			DependsOn: []string{"hop_1_crypto_send"},
		},
		StepExecution: agg.StepExecution{
			StepRef:  "hop_1_crypto_observe",
			StepCode: "hop.1.crypto.observe",
			Attempt:  1,
		},
	})
	if err != nil {
		t.Fatalf("ExecuteObserveConfirm returned error: %v", err)
	}
	if out == nil {
		t.Fatal("expected output")
	}
	if !out.Async {
		t.Fatal("expected async observe output")
	}
	if got, want := out.StepExecution.State, agg.StepStateRunning; got != want {
		t.Fatalf("state mismatch: got=%q want=%q", got, want)
	}
	if got, want := len(out.StepExecution.ExternalRefs), 2; got != want {
		t.Fatalf("external refs count mismatch: got=%d want=%d", got, want)
	}
	if got, want := out.StepExecution.ExternalRefs[0].Kind, "operation_ref"; got != want {
		t.Fatalf("first external ref kind mismatch: got=%q want=%q", got, want)
	}
	if got, want := out.StepExecution.ExternalRefs[0].Ref, "op-1"; got != want {
		t.Fatalf("first external ref value mismatch: got=%q want=%q", got, want)
	}
	if out.StepExecution.ExecutedMoney == nil {
		t.Fatal("expected executed money to be inherited from dependency")
	}
	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 inherited from dependency")
	}
	if got, want := out.StepExecution.ConvertedMoney.Amount, "95.00"; got != want {
		t.Fatalf("converted money amount mismatch: got=%q want=%q", got, want)
	}
	if got, want := out.StepExecution.ConvertedMoney.Currency, "EUR"; got != want {
		t.Fatalf("converted money currency mismatch: got=%q want=%q", got, want)
	}
}
