package voucher

import (
	"errors"
	"testing"
)

func TestParseRules_SimpleSku(t *testing.T) {
	rs, err := ParseRules("sku=ABC123|XYZ999|def456")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(rs.Conditions) != 1 {
		t.Fatalf("expected 1 condition got %d", len(rs.Conditions))
	}
	c := rs.Conditions[0]
	if c.Kind != RuleSku {
		t.Fatalf("expected kind sku got %s", c.Kind)
	}
	if len(c.StringVals) != 3 {
		t.Fatalf("expected 3 sku values got %d", len(c.StringVals))
	}
	want := []string{"ABC123", "XYZ999", "def456"}
	for i, v := range want {
		if c.StringVals[i] != v {
			t.Fatalf("expected sku[%d]=%s got %s", i, v, c.StringVals[i])
		}
	}
}

func TestParseRules_CategoryAndSkuMixedSeparators(t *testing.T) {
	rs, err := ParseRules("  category=Shoes|Bags ; sku= A | B , min_total>=1000\nmin_item_price>=500")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(rs.Conditions) != 4 {
		t.Fatalf("expected 4 conditions got %d", len(rs.Conditions))
	}

	kinds := []RuleKind{RuleCategory, RuleSku, RuleMinTotal, RuleMinItemPrice}
	for i, k := range kinds {
		if rs.Conditions[i].Kind != k {
			t.Fatalf("expected condition[%d] kind %s got %s", i, k, rs.Conditions[i].Kind)
		}
	}

	// Validate numeric thresholds
	if rs.Conditions[2].MinValue == nil || *rs.Conditions[2].MinValue != 1000 {
		t.Fatalf("expected min_total>=1000 got %+v", rs.Conditions[2])
	}
	if rs.Conditions[3].MinValue == nil || *rs.Conditions[3].MinValue != 500 {
		t.Fatalf("expected min_item_price>=500 got %+v", rs.Conditions[3])
	}
}

func TestParseRules_Empty(t *testing.T) {
	_, err := ParseRules("   \n  ")
	if !errors.Is(err, ErrEmptyExpression) {
		t.Fatalf("expected ErrEmptyExpression got %v", err)
	}
}

func TestParseRules_Invalid(t *testing.T) {
	_, err := ParseRules("unknown=foo")
	if err == nil {
		t.Fatal("expected error for unknown key")
	}
	_, err = ParseRules("min_total>100") // wrong operator
	if err == nil {
		t.Fatal("expected error for wrong operator")
	}
	_, err = ParseRules("min_total>=") // missing value
	if err == nil {
		t.Fatal("expected error for missing numeric value")
	}
}

func TestRuleSet_Applies(t *testing.T) {
	rs := MustParseRules("sku=ABC123|XYZ999; category=Shoes|min_total>=10000; min_item_price>=3000")

	ctx := EvalContext{
		Items: []Item{
			{Sku: "ABC123", Category: "Shoes", UnitPrice: 2500},
			{Sku: "FFF000", Category: "Accessories", UnitPrice: 3200},
		},
		CartTotalInc: 12000,
	}

	if !rs.Applies(ctx) {
		t.Fatalf("expected rules to apply")
	}

	// Fail due to missing sku/category
	ctx2 := EvalContext{
		Items: []Item{
			{Sku: "NOPE", Category: "Different", UnitPrice: 4000},
		},
		CartTotalInc: 20000,
	}
	if rs.Applies(ctx2) {
		t.Fatalf("expected rules NOT to apply (sku/category mismatch)")
	}

	// Fail due to min_total
	ctx3 := EvalContext{
		Items: []Item{
			{Sku: "ABC123", Category: "Shoes", UnitPrice: 2500},
			{Sku: "FFF000", Category: "Accessories", UnitPrice: 3200},
		},
		CartTotalInc: 9000,
	}
	if rs.Applies(ctx3) {
		t.Fatalf("expected rules NOT to apply (min_total not reached)")
	}

	// Fail due to min_item_price (no item >=3000)
	ctx4 := EvalContext{
		Items: []Item{
			{Sku: "ABC123", Category: "Shoes", UnitPrice: 2500},
			{Sku: "FFF000", Category: "Accessories", UnitPrice: 2800},
		},
		CartTotalInc: 15000,
	}
	if rs.Applies(ctx4) {
		t.Fatalf("expected rules NOT to apply (min_item_price not satisfied)")
	}
}

func TestRuleSet_Applies_CaseInsensitive(t *testing.T) {
	rs := MustParseRules("SKU=abc123|xyz999; CATEGORY=Shoes")
	ctx := EvalContext{
		Items: []Item{
			{Sku: "AbC123", Category: "shoes", UnitPrice: 1000},
		},
		CartTotalInc: 1000,
	}
	if !rs.Applies(ctx) {
		t.Fatalf("expected rules to apply (case-insensitive match)")
	}
}

func TestDescribe(t *testing.T) {
	rs := MustParseRules("sku=A|B|min_total>=500")
	desc := rs.Describe()
	// Loose assertions to avoid over-specification
	if desc == "" {
		t.Fatalf("expected non-empty description")
	}
	if !(contains(desc, "sku") && contains(desc, "min_total")) {
		t.Fatalf("description missing expected parts: %s", desc)
	}
}

func contains(haystack, needle string) bool {
	return len(haystack) >= len(needle) && indexOf(haystack, needle) >= 0
}

// Simple substring search (avoid importing strings to show intent explicitly here)
func indexOf(s, sub string) int {
outer:
	for i := 0; i+len(sub) <= len(s); i++ {
		for j := 0; j < len(sub); j++ {
			if s[i+j] != sub[j] {
				continue outer
			}
		}
		return i
	}
	return -1
}

func TestMustParseRules_Panics(t *testing.T) {
	defer func() {
		if r := recover(); r == nil {
			t.Fatalf("expected panic for invalid expression")
		}
	}()
	MustParseRules("~~ totally invalid ~~")
}
