package psvc

import (
	"testing"

	"github.com/tech/sendico/pkg/discovery"
)

func TestNormalizeBatchOptimizationPolicy_DefaultMode_NoOptimization(t *testing.T) {
	got := normalizeBatchOptimizationPolicy(BatchOptimizationPolicy{})
	if got.DefaultMode != BatchOptimizationModeNoOptimization {
		t.Fatalf("default_mode mismatch: got=%q want=%q", got.DefaultMode, BatchOptimizationModeNoOptimization)
	}
	if got.Rules != nil {
		t.Fatalf("expected no rules, got=%d", len(got.Rules))
	}
}

func TestNormalizeBatchOptimizationPolicy_DropsInvalidRules(t *testing.T) {
	got := normalizeBatchOptimizationPolicy(BatchOptimizationPolicy{
		DefaultMode: "merge_by_destination",
		Rules: []BatchOptimizationRule{
			{
				ID:   "missing-rail",
				Mode: BatchOptimizationModeMergeByDestination,
				Match: BatchOptimizationMatch{
					Rail: "unknown",
				},
			},
			{
				ID:   "invalid-mode",
				Mode: "x",
				Match: BatchOptimizationMatch{
					Rail: discovery.RailCrypto,
				},
			},
			{
				ID:   "valid",
				Mode: BatchOptimizationModeMergeByDestination,
				Match: BatchOptimizationMatch{
					Rail: discovery.RailCrypto,
				},
			},
		},
	})

	if got.DefaultMode != BatchOptimizationModeMergeByDestination {
		t.Fatalf("default_mode mismatch: got=%q want=%q", got.DefaultMode, BatchOptimizationModeMergeByDestination)
	}
	if len(got.Rules) != 1 {
		t.Fatalf("rules count mismatch: got=%d want=%d", len(got.Rules), 1)
	}
	if got.Rules[0].ID != "valid" {
		t.Fatalf("rule id mismatch: got=%q want=%q", got.Rules[0].ID, "valid")
	}
	if got, want := got.Rules[0].GroupBy, BatchOptimizationGroupingOperationDestination; got != want {
		t.Fatalf("group_by mismatch: got=%q want=%q", got, want)
	}
}

func TestBatchOptimizationPolicy_Select_UsesBestSpecificityThenPriority(t *testing.T) {
	policy := BatchOptimizationPolicy{
		DefaultMode: BatchOptimizationModeNoOptimization,
		Rules: []BatchOptimizationRule{
			{
				ID:       "crypto-generic",
				Priority: 10,
				Mode:     BatchOptimizationModeMergeByDestination,
				Match: BatchOptimizationMatch{
					Rail: discovery.RailCrypto,
				},
			},
			{
				ID:       "crypto-network",
				Priority: 5,
				Mode:     BatchOptimizationModeNoOptimization,
				Match: BatchOptimizationMatch{
					Rail:     discovery.RailCrypto,
					Networks: []string{"tron_nile"},
				},
			},
			{
				ID:       "crypto-network-priority",
				Priority: 20,
				Mode:     BatchOptimizationModeMergeByDestination,
				Match: BatchOptimizationMatch{
					Rail:     discovery.RailCrypto,
					Networks: []string{"tron_nile"},
				},
			},
		},
	}

	out := policy.Select(BatchOptimizationContext{
		Rail:    discovery.RailCrypto,
		Network: "TRON_NILE",
	})
	if !out.Matched {
		t.Fatal("expected matched rule")
	}
	if out.RuleID != "crypto-network-priority" {
		t.Fatalf("rule id mismatch: got=%q want=%q", out.RuleID, "crypto-network-priority")
	}
	if out.Mode != BatchOptimizationModeMergeByDestination {
		t.Fatalf("mode mismatch: got=%q want=%q", out.Mode, BatchOptimizationModeMergeByDestination)
	}
	if got, want := out.GroupBy, BatchOptimizationGroupingOperationDestination; got != want {
		t.Fatalf("group_by mismatch: got=%q want=%q", got, want)
	}
	if got, want := string(out.MatchRail), string(discovery.RailCrypto); got != want {
		t.Fatalf("match rail mismatch: got=%q want=%q", got, want)
	}
}

func TestBatchOptimizationPolicy_Select_FallsBackToDefault(t *testing.T) {
	policy := BatchOptimizationPolicy{
		DefaultMode: BatchOptimizationModeNoOptimization,
		Rules: []BatchOptimizationRule{
			{
				ID:   "crypto-only",
				Mode: BatchOptimizationModeMergeByDestination,
				Match: BatchOptimizationMatch{
					Rail: discovery.RailCrypto,
				},
			},
		},
	}

	out := policy.Select(BatchOptimizationContext{Rail: discovery.RailCardPayout})
	if out.Matched {
		t.Fatalf("unexpected matched rule: %q", out.RuleID)
	}
	if out.Mode != BatchOptimizationModeNoOptimization {
		t.Fatalf("mode mismatch: got=%q want=%q", out.Mode, BatchOptimizationModeNoOptimization)
	}
	if got, want := out.GroupBy, BatchOptimizationGroupingOperationDestination; got != want {
		t.Fatalf("default group_by mismatch: got=%q want=%q", got, want)
	}
}

func TestBatchOptimizationPolicy_Select_MatchesAmountRange(t *testing.T) {
	policy := BatchOptimizationPolicy{
		DefaultMode: BatchOptimizationModeNoOptimization,
		Rules: []BatchOptimizationRule{
			{
				ID:   "crypto-large-usdt",
				Mode: BatchOptimizationModeMergeByDestination,
				Match: BatchOptimizationMatch{
					Rail: discovery.RailCrypto,
					Amount: &BatchOptimizationAmountRange{
						Min:      "100",
						Currency: "USDT",
					},
				},
			},
		},
	}

	matched := policy.Select(BatchOptimizationContext{
		Rail:     discovery.RailCrypto,
		Amount:   "120",
		Currency: "USDT",
	})
	if !matched.Matched {
		t.Fatal("expected amount rule match")
	}

	notMatched := policy.Select(BatchOptimizationContext{
		Rail:     discovery.RailCrypto,
		Amount:   "50",
		Currency: "USDT",
	})
	if notMatched.Matched {
		t.Fatalf("expected no amount rule match, got rule=%q", notMatched.RuleID)
	}
	if notMatched.Mode != BatchOptimizationModeNoOptimization {
		t.Fatalf("default mode mismatch: got=%q want=%q", notMatched.Mode, BatchOptimizationModeNoOptimization)
	}
}

func TestBatchOptimizationPolicy_Select_ReturnsConfiguredGroupBy(t *testing.T) {
	policy := BatchOptimizationPolicy{
		DefaultMode: BatchOptimizationModeNoOptimization,
		Rules: []BatchOptimizationRule{
			{
				ID:      "crypto-by-rail-target",
				Mode:    BatchOptimizationModeMergeByDestination,
				GroupBy: BatchOptimizationGroupingRailTarget,
				Match: BatchOptimizationMatch{
					Rail: discovery.RailCrypto,
				},
			},
		},
	}
	out := policy.Select(BatchOptimizationContext{Rail: discovery.RailCrypto})
	if !out.Matched {
		t.Fatal("expected matched rule")
	}
	if got, want := out.GroupBy, BatchOptimizationGroupingRailTarget; got != want {
		t.Fatalf("group_by mismatch: got=%q want=%q", got, want)
	}
}
