package xplan

import (
	"errors"
	"github.com/tech/sendico/pkg/discovery"
	"testing"

	"github.com/tech/sendico/payments/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
)

func TestCompile_PolicyOverrideByRailPair(t *testing.T) {
	compiler := New()

	var cardRail model.Rail = discovery.RailCardPayout
	var ledgerRail model.Rail = discovery.RailLedger

	graph, err := compiler.Compile(Input{
		IntentSnapshot: testIntent(model.PaymentKindPayout),
		QuoteSnapshot: &model.PaymentQuoteSnapshot{
			Route: &paymenttypes.QuoteRouteSpecification{
				Hops: []*paymenttypes.QuoteRouteHop{
					{Index: 10, Rail: "CRYPTO", Role: paymenttypes.QuoteRouteHopRoleSource},
					{Index: 20, Rail: "CARD", Role: paymenttypes.QuoteRouteHopRoleDestination},
				},
			},
		},
		Policies: []Policy{
			{
				ID: "crypto-to-card-override",
				Match: EdgeMatch{
					Source: EndpointMatch{Rail: railPtr(discovery.RailCrypto)},
					Target: EndpointMatch{Rail: railPtr(discovery.RailCardPayout)},
				},
				Steps: []PolicyStep{
					{Code: "custom.review", Action: discovery.RailOperationMove, Rail: &ledgerRail},
					{Code: "custom.submit", Action: discovery.RailOperationSend, Rail: &cardRail, Visibility: model.ReportVisibilityUser},
				},
				Success: []PolicyStep{
					{Code: "custom.finalize", Action: discovery.RailOperationDebit, Rail: &ledgerRail},
				},
				Failure: []PolicyStep{
					{Code: "custom.release", Action: discovery.RailOperationRelease, Rail: &ledgerRail},
				},
			},
		},
	})
	if err != nil {
		t.Fatalf("Compile returned error: %v", err)
	}

	if len(graph.Steps) != 4 {
		t.Fatalf("expected 4 steps, got %d", len(graph.Steps))
	}
	assertStep(t, graph.Steps[0], "custom.review", discovery.RailOperationMove, discovery.RailLedger, model.ReportVisibilityHidden)
	assertStep(t, graph.Steps[1], "custom.submit", discovery.RailOperationSend, discovery.RailCardPayout, model.ReportVisibilityUser)
	assertStep(t, graph.Steps[2], "custom.finalize", discovery.RailOperationDebit, discovery.RailLedger, model.ReportVisibilityHidden)
	assertStep(t, graph.Steps[3], "custom.release", discovery.RailOperationRelease, discovery.RailLedger, model.ReportVisibilityHidden)

	if graph.Steps[2].CommitPolicy != model.CommitPolicyAfterSuccess {
		t.Fatalf("expected custom.finalize AFTER_SUCCESS, got %q", graph.Steps[2].CommitPolicy)
	}
	if graph.Steps[3].CommitPolicy != model.CommitPolicyAfterFailure {
		t.Fatalf("expected custom.release AFTER_FAILURE, got %q", graph.Steps[3].CommitPolicy)
	}
}

func TestCompile_PolicyPriorityAndCustodyMatching(t *testing.T) {
	compiler := New()
	var cardRail model.Rail = discovery.RailCardPayout

	on := true
	external := CustodyExternal

	graph, err := compiler.Compile(Input{
		IntentSnapshot: testIntent(model.PaymentKindPayout),
		QuoteSnapshot: &model.PaymentQuoteSnapshot{
			Route: &paymenttypes.QuoteRouteSpecification{
				Hops: []*paymenttypes.QuoteRouteHop{
					{Index: 10, Rail: "CRYPTO", Role: paymenttypes.QuoteRouteHopRoleSource},
					{Index: 20, Rail: "CARD", Role: paymenttypes.QuoteRouteHopRoleDestination},
				},
			},
		},
		Policies: []Policy{
			{
				ID:       "generic-external",
				Enabled:  &on,
				Priority: 1,
				Match: EdgeMatch{
					Source: EndpointMatch{Custody: &external},
					Target: EndpointMatch{Custody: &external},
				},
				Steps: []PolicyStep{{Code: "generic.submit", Action: discovery.RailOperationSend, Rail: &cardRail}},
			},
			{
				ID:       "specific-crypto-card",
				Enabled:  &on,
				Priority: 10,
				Match: EdgeMatch{
					Source: EndpointMatch{Rail: railPtr(discovery.RailCrypto), Custody: &external},
					Target: EndpointMatch{Rail: railPtr(discovery.RailCardPayout), Custody: &external},
				},
				Steps: []PolicyStep{{Code: "specific.submit", Action: discovery.RailOperationSend, Rail: &cardRail}},
			},
		},
	})
	if err != nil {
		t.Fatalf("Compile returned error: %v", err)
	}

	if len(graph.Steps) != 1 {
		t.Fatalf("expected 1 policy step, got %d", len(graph.Steps))
	}
	if got, want := graph.Steps[0].StepCode, "specific.submit"; got != want {
		t.Fatalf("expected high-priority specific policy, got %q", got)
	}
}

func TestCompile_IndicativeRejected(t *testing.T) {
	compiler := New()

	_, err := compiler.Compile(Input{
		IntentSnapshot: testIntent(model.PaymentKindPayout),
		QuoteSnapshot: &model.PaymentQuoteSnapshot{
			Route: &paymenttypes.QuoteRouteSpecification{
				Rail: "CRYPTO",
			},
			ExecutionConditions: &paymenttypes.QuoteExecutionConditions{
				Readiness: paymenttypes.QuoteExecutionReadinessIndicative,
			},
		},
	})
	if !errors.Is(err, ErrNotExecutable) {
		t.Fatalf("expected ErrNotExecutable, got %v", err)
	}
}

func TestCompile_ValidationErrors(t *testing.T) {
	compiler := New()

	enabled := true

	tests := []struct {
		name string
		in   Input
	}{
		{
			name: "missing intent",
			in: Input{
				QuoteSnapshot: &model.PaymentQuoteSnapshot{
					Route: &paymenttypes.QuoteRouteSpecification{Rail: "CRYPTO"},
				},
			},
		},
		{
			name: "missing quote",
			in: Input{
				IntentSnapshot: testIntent(model.PaymentKindPayout),
			},
		},
		{
			name: "missing route",
			in: Input{
				IntentSnapshot: testIntent(model.PaymentKindPayout),
				QuoteSnapshot:  &model.PaymentQuoteSnapshot{},
			},
		},
		{
			name: "unknown hop rail",
			in: Input{
				IntentSnapshot: testIntent(model.PaymentKindPayout),
				QuoteSnapshot: &model.PaymentQuoteSnapshot{
					Route: &paymenttypes.QuoteRouteSpecification{
						Hops: []*paymenttypes.QuoteRouteHop{{Index: 1, Rail: "UNKNOWN"}},
					},
				},
			},
		},
		{
			name: "invalid policy step action",
			in: Input{
				IntentSnapshot: testIntent(model.PaymentKindPayout),
				QuoteSnapshot: &model.PaymentQuoteSnapshot{
					Route: &paymenttypes.QuoteRouteSpecification{
						Hops: []*paymenttypes.QuoteRouteHop{
							{Index: 1, Rail: "LEDGER", Role: paymenttypes.QuoteRouteHopRoleSource},
							{Index: 2, Rail: "CARD", Role: paymenttypes.QuoteRouteHopRoleDestination},
						},
					},
				},
				Policies: []Policy{
					{
						ID:       "bad-policy",
						Enabled:  &enabled,
						Priority: 1,
						Match: EdgeMatch{
							Source: EndpointMatch{Rail: railPtr(discovery.RailLedger)},
							Target: EndpointMatch{Rail: railPtr(discovery.RailCardPayout)},
						},
						Steps: []PolicyStep{
							{Code: "bad.step", Action: discovery.RailOperationUnspecified},
						},
					},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := compiler.Compile(tt.in)
			if !errors.Is(err, merrors.ErrInvalidArg) {
				t.Fatalf("expected invalid argument, got %v", err)
			}
		})
	}
}
