package promotions

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
)

// -----------------------------
// Enum-like string types
// -----------------------------

type ConditionOperator string

const (
	OpEquals         ConditionOperator = "equals"
	OpNotEquals      ConditionOperator = "not_equals"
	OpGreaterThan    ConditionOperator = "greater_than"
	OpLessThan       ConditionOperator = "less_than"
	OpGreaterOrEqual ConditionOperator = "greater_or_equal"
	OpLessOrEqual    ConditionOperator = "less_or_equal"
	OpContains       ConditionOperator = "contains"
	OpNotContains    ConditionOperator = "not_contains"
	OpIn             ConditionOperator = "in"
	OpNotIn          ConditionOperator = "not_in"
)

type ConditionType string

const (
	CondCartTotal        ConditionType = "cart_total"
	CondItemQuantity     ConditionType = "item_quantity"
	CondCustomerSegment  ConditionType = "customer_segment"
	CondProductCategory  ConditionType = "product_category"
	CondProductID        ConditionType = "product_id"
	CondCustomerLifetime ConditionType = "customer_lifetime_value"
	CondOrderCount       ConditionType = "order_count"
	CondDateRange        ConditionType = "date_range"
	CondDayOfWeek        ConditionType = "day_of_week"
	CondTimeOfDay        ConditionType = "time_of_day"
	CondGroup            ConditionType = "group" // synthetic value for groups
)

type ActionType string

const (
	ActionPercentageDiscount ActionType = "percentage_discount"
	ActionFixedDiscount      ActionType = "fixed_discount"
	ActionFreeShipping       ActionType = "free_shipping"
	ActionBuyXGetY           ActionType = "buy_x_get_y"
	ActionTieredDiscount     ActionType = "tiered_discount"
	ActionBundleDiscount     ActionType = "bundle_discount"
)

type LogicOperator string

const (
	LogicAND LogicOperator = "AND"
	LogicOR  LogicOperator = "OR"
)

type PromotionStatus string

const (
	StatusActive    PromotionStatus = "active"
	StatusInactive  PromotionStatus = "inactive"
	StatusScheduled PromotionStatus = "scheduled"
	StatusExpired   PromotionStatus = "expired"
)

// -----------------------------
// Condition Value (union)
// -----------------------------
//
// Represents: string | number | []string | []number
// We store raw JSON and lazily interpret.

type ConditionValue struct {
	Raw json.RawMessage
}

func (cv *ConditionValue) UnmarshalJSON(b []byte) error {
	if len(b) == 0 {
		return errors.New("empty ConditionValue")
	}
	// Just store raw; interpretation happens via helpers.
	cv.Raw = append(cv.Raw[0:0], b...)
	return nil
}

// Helpers to interpret value:
func (cv ConditionValue) AsString() (string, bool) {
	// Treat explicit JSON null as invalid
	if string(cv.Raw) == "null" {
		return "", false
	}
	var s string
	if err := json.Unmarshal(cv.Raw, &s); err == nil {
		return s, true
	}
	return "", false
}

func (cv ConditionValue) AsFloat64() (float64, bool) {
	if string(cv.Raw) == "null" {
		return 0, false
	}
	var f float64
	if err := json.Unmarshal(cv.Raw, &f); err == nil {
		return f, true
	}
	// Attempt integer decode into float64
	var i int64
	if err := json.Unmarshal(cv.Raw, &i); err == nil {
		return float64(i), true
	}
	return 0, false
}

func (cv ConditionValue) AsStringSlice() ([]string, bool) {
	if string(cv.Raw) == "null" {
		return nil, false
	}
	var arr []string
	if err := json.Unmarshal(cv.Raw, &arr); err == nil {
		return arr, true
	}
	return nil, false
}

func (cv ConditionValue) AsFloat64Slice() ([]float64, bool) {
	if string(cv.Raw) == "null" {
		return nil, false
	}
	var arrNum []float64
	if err := json.Unmarshal(cv.Raw, &arrNum); err == nil {
		return arrNum, true
	}
	// Try []int -> []float64
	var arrInt []int64
	if err := json.Unmarshal(cv.Raw, &arrInt); err == nil {
		out := make([]float64, len(arrInt))
		for i, v := range arrInt {
			out[i] = float64(v)
		}
		return out, true
	}
	return nil, false
}

func (cv ConditionValue) String() string {
	if s, ok := cv.AsString(); ok {
		return s
	}
	if f, ok := cv.AsFloat64(); ok {
		return strconv.FormatFloat(f, 'f', -1, 64)
	}
	if ss, ok := cv.AsStringSlice(); ok {
		return fmt.Sprintf("%v", ss)
	}
	if fs, ok := cv.AsFloat64Slice(); ok {
		return fmt.Sprintf("%v", fs)
	}
	return string(cv.Raw)
}

// -----------------------------
// BaseCondition
// -----------------------------

type BaseCondition struct {
	ID       string            `json:"id"`
	Type     ConditionType     `json:"type"`
	Operator ConditionOperator `json:"operator"`
	Value    ConditionValue    `json:"value"`
	Label    *string           `json:"label,omitempty"`
}

func (b BaseCondition) IsGroup() bool { return false }

// -----------------------------
// ConditionGroup
// -----------------------------

type ConditionGroup struct {
	ID         string        `json:"id"`
	Type       string        `json:"type"` // always "group"
	Operator   LogicOperator `json:"operator"`
	Conditions Conditions    `json:"conditions"`
}

// Custom unmarshaller ensures nested polymorphic conditions are decoded
// using the Conditions type (which applies the raw element discriminator).
func (g *ConditionGroup) UnmarshalJSON(b []byte) error {
	type alias struct {
		ID         string          `json:"id"`
		Type       string          `json:"type"`
		Operator   LogicOperator   `json:"operator"`
		Conditions json.RawMessage `json:"conditions"`
	}
	var a alias
	if err := json.Unmarshal(b, &a); err != nil {
		return err
	}
	// Basic validation
	if a.Type != "group" {
		return fmt.Errorf("ConditionGroup expected type 'group', got %q", a.Type)
	}

	var conds Conditions
	if len(a.Conditions) > 0 {
		if err := json.Unmarshal(a.Conditions, &conds); err != nil {
			return err
		}
	}

	g.ID = a.ID
	g.Type = a.Type
	g.Operator = a.Operator
	g.Conditions = conds
	return nil
}

func (g ConditionGroup) IsGroup() bool { return true }

// -----------------------------
// Condition interface + slice
// -----------------------------

type Condition interface {
	IsGroup() bool
}

// Internal wrapper to help decode each element.
type rawCond struct {
	Type string `json:"type"`
}

// Custom unmarshaler for a Condition slice
type Conditions []Condition

func (cs *Conditions) UnmarshalJSON(b []byte) error {
	var rawList []json.RawMessage
	if err := json.Unmarshal(b, &rawList); err != nil {
		return err
	}
	out := make([]Condition, 0, len(rawList))
	for _, elem := range rawList {
		var hdr rawCond
		if err := json.Unmarshal(elem, &hdr); err != nil {
			return err
		}
		if hdr.Type == "group" {
			var grp ConditionGroup
			if err := json.Unmarshal(elem, &grp); err != nil {
				return err
			}
			// Recursively decode grp.Conditions (already handled because field type is []Condition)
			out = append(out, grp)
		} else {
			var bc BaseCondition
			if err := json.Unmarshal(elem, &bc); err != nil {
				return err
			}
			out = append(out, bc)
		}
	}
	*cs = out
	return nil
}

// -----------------------------
// Bundle Structures
// -----------------------------

type BundleQualifyingRules struct {
	Type  string      `json:"type"`  // "category" | "product_ids" | "tag" | "all"
	Value interface{} `json:"value"` // string or []string
}

type BundleContainer struct {
	ID              string                `json:"id"`
	Name            string                `json:"name"`
	Quantity        int                   `json:"quantity"`
	SelectionType   string                `json:"selectionType"` // "any" | "specific"
	QualifyingRules BundleQualifyingRules `json:"qualifyingRules"`
	AllowedProducts []string              `json:"allowedProducts,omitempty"`
}

type BundlePricing struct {
	Type  string  `json:"type"` // "fixed_price" | "percentage_discount" | "fixed_discount"
	Value float64 `json:"value"`
}

type BundleConfig struct {
	Containers           []BundleContainer `json:"containers"`
	Pricing              BundlePricing     `json:"pricing"`
	RequireAllContainers bool              `json:"requireAllContainers"`
}

// -----------------------------
// Action
// -----------------------------

type Action struct {
	ID           string                 `json:"id"`
	Type         ActionType             `json:"type"`
	Value        interface{}            `json:"value"` // number or string
	Config       map[string]interface{} `json:"config,omitempty"`
	BundleConfig *BundleConfig          `json:"bundleConfig,omitempty"`
	Label        *string                `json:"label,omitempty"`
}

// -----------------------------
// Promotion Rule
// -----------------------------

type PromotionRule struct {
	ID          string          `json:"id"`
	Name        string          `json:"name"`
	Description string          `json:"description"`
	Status      PromotionStatus `json:"status"`
	Priority    int             `json:"priority"`
	StartDate   string          `json:"startDate"`
	EndDate     *string         `json:"endDate"` // null -> nil
	Conditions  Conditions      `json:"conditions"`
	Actions     []Action        `json:"actions"`
	UsageLimit  *int            `json:"usageLimit,omitempty"`
	UsageCount  int             `json:"usageCount"`
	CreatedAt   string          `json:"createdAt"`
	UpdatedAt   string          `json:"updatedAt"`
	CreatedBy   string          `json:"createdBy"`
	Tags        []string        `json:"tags"`
}

// -----------------------------
// Promotion Stats
// -----------------------------

type PromotionStats struct {
	TotalPromotions  int     `json:"totalPromotions"`
	ActivePromotions int     `json:"activePromotions"`
	TotalRevenue     float64 `json:"totalRevenue"`
	TotalOrders      int     `json:"totalOrders"`
	AverageDiscount  float64 `json:"averageDiscount"`
}

// -----------------------------
// Utility: Decode array of rules
// -----------------------------

func DecodePromotionRules(data []byte) ([]PromotionRule, error) {
	var rules []PromotionRule
	if err := json.Unmarshal(data, &rules); err != nil {
		return nil, err
	}
	return rules, nil
}

// Example helper to inspect conditions programmatically.
func WalkConditions(conds []Condition, fn func(c Condition) bool) {
	for _, c := range conds {
		if !fn(c) {
			return
		}
		if grp, ok := c.(ConditionGroup); ok {
			WalkConditions(grp.Conditions, fn)
		}
	}
}
