package promotions

import (
	"encoding/json"
	"testing"
)

// sampleJSON mirrors the user's full example data (all three rules)
var sampleJSON = []byte(`[
  {
    "id": "1",
    "name": "Summer Sale 2024",
    "description": "20% off on all summer items",
    "status": "active",
    "priority": 1,
    "startDate": "2024-06-01",
    "endDate": "2024-08-31",
    "conditions": [
      {
        "id": "group1",
        "type": "group",
        "operator": "AND",
        "conditions": [
          {
            "id": "c1",
            "type": "product_category",
            "operator": "in",
            "value": ["summer", "beachwear"],
            "label": "Product category is Summer or Beachwear"
          },
          {
            "id": "c2",
            "type": "cart_total",
            "operator": "greater_or_equal",
            "value": 50,
            "label": "Cart total is at least $50"
          }
        ]
      }
    ],
    "actions": [
      {
        "id": "a1",
        "type": "percentage_discount",
        "value": 20,
        "label": "20% discount"
      }
    ],
    "usageLimit": 1000,
    "usageCount": 342,
    "createdAt": "2024-05-15T10:00:00Z",
    "updatedAt": "2024-05-20T14:30:00Z",
    "createdBy": "admin@example.com",
    "tags": ["seasonal", "summer"]
  },
  {
    "id": "2",
    "name": "VIP Customer Exclusive",
    "description": "Free shipping for VIP customers",
    "status": "active",
    "priority": 2,
    "startDate": "2024-01-01",
    "endDate": null,
    "conditions": [
      {
        "id": "c3",
        "type": "customer_segment",
        "operator": "equals",
        "value": "vip",
        "label": "Customer segment is VIP"
      }
    ],
    "actions": [
      {
        "id": "a2",
        "type": "free_shipping",
        "value": 0,
        "label": "Free shipping"
      }
    ],
    "usageCount": 1523,
    "createdAt": "2023-12-15T09:00:00Z",
    "updatedAt": "2024-01-05T11:20:00Z",
    "createdBy": "marketing@example.com",
    "tags": ["vip", "loyalty"]
  },
  {
    "id": "3",
    "name": "Buy 2 Get 1 Free",
    "description": "Buy 2 items, get the cheapest one free",
    "status": "scheduled",
    "priority": 3,
    "startDate": "2024-12-01",
    "endDate": "2024-12-25",
    "conditions": [
      {
        "id": "c4",
        "type": "item_quantity",
        "operator": "greater_or_equal",
        "value": 3,
        "label": "Cart has at least 3 items"
      }
    ],
    "actions": [
      {
        "id": "a3",
        "type": "buy_x_get_y",
        "value": 0,
        "config": { "buy": 2, "get": 1, "discount": 100 },
        "label": "Buy 2 Get 1 Free"
      }
    ],
    "usageCount": 0,
    "createdAt": "2024-11-01T08:00:00Z",
    "updatedAt": "2024-11-01T08:00:00Z",
    "createdBy": "admin@example.com",
    "tags": ["holiday", "christmas"]
  }
]`)

func TestDecodePromotionRulesBasic(t *testing.T) {
	rules, err := DecodePromotionRules(sampleJSON)
	if err != nil {
		t.Fatalf("DecodePromotionRules failed: %v", err)
	}
	if len(rules) != 3 {
		t.Fatalf("expected 3 rules, got %d", len(rules))
	}

	// Rule 1 checks
	r1 := rules[0]
	if r1.ID != "1" {
		t.Errorf("rule[0].ID = %s, want 1", r1.ID)
	}
	if r1.Status != StatusActive {
		t.Errorf("rule[0].Status = %s, want %s", r1.Status, StatusActive)
	}
	if r1.EndDate == nil || *r1.EndDate != "2024-08-31" {
		t.Errorf("rule[0].EndDate = %v, want 2024-08-31", r1.EndDate)
	}
	if r1.UsageLimit == nil || *r1.UsageLimit != 1000 {
		t.Errorf("rule[0].UsageLimit = %v, want 1000", r1.UsageLimit)
	}

	// Rule 2 checks
	r2 := rules[1]
	if r2.ID != "2" {
		t.Errorf("rule[1].ID = %s, want 2", r2.ID)
	}
	if r2.EndDate != nil {
		t.Errorf("rule[1].EndDate should be nil (from null), got %v", *r2.EndDate)
	}
	if r2.UsageLimit != nil {
		t.Errorf("rule[1].UsageLimit should be nil (missing), got %v", *r2.UsageLimit)
	}
}

func TestConditionDecoding(t *testing.T) {
	rules, err := DecodePromotionRules(sampleJSON)
	if err != nil {
		t.Fatalf("DecodePromotionRules failed: %v", err)
	}
	r1 := rules[0]
	if len(r1.Conditions) != 1 {
		t.Fatalf("expected 1 top-level condition group, got %d", len(r1.Conditions))
	}

	grp, ok := r1.Conditions[0].(ConditionGroup)
	if !ok {
		t.Fatalf("top-level condition is not a group, type=%T", r1.Conditions[0])
	}
	if grp.Operator != LogicAND {
		t.Errorf("group operator = %s, want AND", grp.Operator)
	}

	if len(grp.Conditions) != 2 {
		t.Fatalf("expected 2 child conditions, got %d", len(grp.Conditions))
	}

	// First child: product_category condition with slice value
	c0, ok := grp.Conditions[0].(BaseCondition)
	if !ok {
		t.Fatalf("first child not BaseCondition, got %T", grp.Conditions[0])
	}
	if c0.Type != CondProductCategory {
		t.Errorf("first child type = %s, want %s", c0.Type, CondProductCategory)
	}
	if c0.Operator != OpIn {
		t.Errorf("first child operator = %s, want %s", c0.Operator, OpIn)
	}
	if arr, ok := c0.Value.AsStringSlice(); !ok || len(arr) != 2 || arr[0] != "summer" {
		t.Errorf("expected string slice value [summer,...], got %v", arr)
	}

	// Second child: cart_total numeric
	c1, ok := grp.Conditions[1].(BaseCondition)
	if !ok {
		t.Fatalf("second child not BaseCondition, got %T", grp.Conditions[1])
	}
	if c1.Type != CondCartTotal {
		t.Errorf("second child type = %s, want %s", c1.Type, CondCartTotal)
	}
	if val, ok := c1.Value.AsFloat64(); !ok || val != 50 {
		t.Errorf("expected numeric value 50, got %v (ok=%v)", val, ok)
	}
}

func TestConditionValueHelpers(t *testing.T) {
	tests := []struct {
		name    string
		jsonVal string
		wantStr string
		wantNum *float64
		wantSS  []string
		wantFS  []float64
	}{
		{
			name:    "string value",
			jsonVal: `"vip"`,
			wantStr: "vip",
		},
		{
			name:    "number value",
			jsonVal: `42`,
			wantNum: floatPtr(42),
		},
		{
			name:    "string slice",
			jsonVal: `["a","b"]`,
			wantSS:  []string{"a", "b"},
		},
		{
			name:    "number slice int",
			jsonVal: `[1,2,3]`,
			wantFS:  []float64{1, 2, 3},
		},
		{
			name:    "number slice float",
			jsonVal: `[1.5,2.25]`,
			wantFS:  []float64{1.5, 2.25},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			var cv ConditionValue
			if err := json.Unmarshal([]byte(tc.jsonVal), &cv); err != nil {
				t.Fatalf("unmarshal value failed: %v", err)
			}
			if tc.wantStr != "" {
				if got, ok := cv.AsString(); !ok || got != tc.wantStr {
					t.Errorf("AsString got=%q ok=%v want=%q", got, ok, tc.wantStr)
				}
			}
			if tc.wantNum != nil {
				if got, ok := cv.AsFloat64(); !ok || got != *tc.wantNum {
					t.Errorf("AsFloat64 got=%v ok=%v want=%v", got, ok, *tc.wantNum)
				}
			}
			if tc.wantSS != nil {
				if got, ok := cv.AsStringSlice(); !ok || len(got) != len(tc.wantSS) || got[0] != tc.wantSS[0] {
					t.Errorf("AsStringSlice got=%v ok=%v want=%v", got, ok, tc.wantSS)
				}
			}
			if tc.wantFS != nil {
				if got, ok := cv.AsFloat64Slice(); !ok || len(got) != len(tc.wantFS) {
					t.Errorf("AsFloat64Slice got=%v ok=%v want=%v", got, ok, tc.wantFS)
				} else {
					for i := range got {
						if got[i] != tc.wantFS[i] {
							t.Errorf("AsFloat64Slice[%d]=%v want=%v", i, got[i], tc.wantFS[i])
						}
					}
				}
			}
		})
	}
}

func TestWalkConditionsTraversal(t *testing.T) {
	rules, err := DecodePromotionRules(sampleJSON)
	if err != nil {
		t.Fatalf("DecodePromotionRules failed: %v", err)
	}
	visited := 0
	WalkConditions(rules[0].Conditions, func(c Condition) bool {
		visited++
		return true
	})
	// group + 2 children
	if visited != 3 {
		t.Errorf("expected 3 visited conditions, got %d", visited)
	}
}

func TestActionBundleConfigParsing(t *testing.T) {
	jsonData := []byte(`[
	  {
	    "id": "bundle-1",
	    "name": "Bundle Deal",
	    "description": "Fixed price bundle",
	    "status": "active",
	    "priority": 1,
	    "startDate": "2024-01-01",
	    "endDate": null,
	    "conditions": [],
	    "actions": [
	      {
	        "id": "act-bundle",
	        "type": "bundle_discount",
	        "value": 0,
	        "bundleConfig": {
	          "containers": [
	            {
	              "id": "cont1",
	              "name": "Shoes",
	              "quantity": 2,
	              "selectionType": "any",
	              "qualifyingRules": {
	                "type": "category",
	                "value": "shoes"
	              }
	            },
	            {
	              "id": "cont2",
	              "name": "Socks",
	              "quantity": 3,
	              "selectionType": "specific",
	              "qualifyingRules": {
	                "type": "product_ids",
	                "value": ["sock-1", "sock-2"]
	              },
	              "allowedProducts": ["sock-1","sock-2"]
	            }
	          ],
	          "pricing": {
	            "type": "fixed_price",
	            "value": 49.99
	          },
	          "requireAllContainers": true
	        },
	        "config": { "note": "Bundle applies to footwear + socks" }
	      }
	    ],
	    "usageCount": 0,
	    "createdAt": "2024-01-01T00:00:00Z",
	    "updatedAt": "2024-01-01T00:00:00Z",
	    "createdBy": "bundle@example.com",
	    "tags": ["bundle","footwear"]
	  }
	]`)
	rules, err := DecodePromotionRules(jsonData)
	if err != nil {
		t.Fatalf("decode failed: %v", err)
	}
	if len(rules) != 1 {
		t.Fatalf("expected 1 rule, got %d", len(rules))
	}
	if len(rules[0].Actions) != 1 {
		t.Fatalf("expected 1 action, got %d", len(rules[0].Actions))
	}
	act := rules[0].Actions[0]
	if act.Type != ActionBundleDiscount {
		t.Fatalf("action type = %s, want %s", act.Type, ActionBundleDiscount)
	}
	if act.BundleConfig == nil {
		t.Fatalf("bundleConfig nil")
	}
	if act.BundleConfig.Pricing.Type != "fixed_price" {
		t.Errorf("pricing.type = %s, want fixed_price", act.BundleConfig.Pricing.Type)
	}
	if act.BundleConfig.Pricing.Value != 49.99 {
		t.Errorf("pricing.value = %v, want 49.99", act.BundleConfig.Pricing.Value)
	}
	if !act.BundleConfig.RequireAllContainers {
		t.Errorf("RequireAllContainers expected true")
	}
	if len(act.BundleConfig.Containers) != 2 {
		t.Fatalf("expected 2 containers, got %d", len(act.BundleConfig.Containers))
	}
	if act.Config == nil || act.Config["note"] != "Bundle applies to footwear + socks" {
		t.Errorf("config.note mismatch: %v", act.Config)
	}
}

func TestConditionValueInvalidTypes(t *testing.T) {
	cases := []struct {
		name string
		raw  string
	}{
		{"object", `{}`},
		{"booleanTrue", `true`},
		{"booleanFalse", `false`},
		{"null", `null`},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			var cv ConditionValue
			if err := json.Unmarshal([]byte(tc.raw), &cv); err != nil {
				t.Fatalf("unmarshal failed: %v", err)
			}
			if s, ok := cv.AsString(); ok {
				t.Errorf("AsString unexpectedly succeeded (%q) for %s", s, tc.name)
			}
			if n, ok := cv.AsFloat64(); ok {
				t.Errorf("AsFloat64 unexpectedly succeeded (%v) for %s", n, tc.name)
			}
			if ss, ok := cv.AsStringSlice(); ok {
				t.Errorf("AsStringSlice unexpectedly succeeded (%v) for %s", ss, tc.name)
			}
			if fs, ok := cv.AsFloat64Slice(); ok {
				t.Errorf("AsFloat64Slice unexpectedly succeeded (%v) for %s", fs, tc.name)
			}
		})
	}
}

func TestPromotionRuleRoundTrip(t *testing.T) {
	orig, err := DecodePromotionRules(sampleJSON)
	if err != nil {
		t.Fatalf("initial decode failed: %v", err)
	}
	data, err := json.Marshal(orig)
	if err != nil {
		t.Fatalf("marshal failed: %v", err)
	}
	round, err := DecodePromotionRules(data)
	if err != nil {
		t.Fatalf("round-trip decode failed: %v", err)
	}
	if len(orig) != len(round) {
		t.Fatalf("rule count mismatch: orig=%d round=%d", len(orig), len(round))
	}
	// spot-check first rule
	if orig[0].Name != round[0].Name {
		t.Errorf("first rule name mismatch: %s vs %s", orig[0].Name, round[0].Name)
	}
	if len(orig[0].Conditions) != len(round[0].Conditions) {
		t.Errorf("first rule condition count mismatch: %d vs %d", len(orig[0].Conditions), len(round[0].Conditions))
	}
}

func floatPtr(f float64) *float64 { return &f }
