package cart

import (
	"encoding/json"
	"slices"
	"sync"
	"time"

	messages "git.tornberg.me/go-cart-actor/pkg/messages"
	"git.tornberg.me/go-cart-actor/pkg/voucher"
)

// Legacy padded [16]byte CartId and its helper methods removed.
// Unified CartId (uint64 with base62 string form) now defined in cart_id.go.

type StockStatus int

type ItemMeta struct {
	Name       string  `json:"name"`
	Brand      string  `json:"brand,omitempty"`
	Category   string  `json:"category,omitempty"`
	Category2  string  `json:"category2,omitempty"`
	Category3  string  `json:"category3,omitempty"`
	Category4  string  `json:"category4,omitempty"`
	Category5  string  `json:"category5,omitempty"`
	SellerId   string  `json:"sellerId,omitempty"`
	SellerName string  `json:"sellerName,omitempty"`
	Image      string  `json:"image,omitempty"`
	Outlet     *string `json:"outlet,omitempty"`
}

type CartItem struct {
	Id          uint32      `json:"id"`
	ItemId      uint32      `json:"itemId,omitempty"`
	ParentId    *uint32     `json:"parentId,omitempty"`
	Sku         string      `json:"sku"`
	Price       Price       `json:"price"`
	TotalPrice  Price       `json:"totalPrice"`
	OrgPrice    *Price      `json:"orgPrice,omitempty"`
	Stock       StockStatus `json:"stock"`
	Quantity    int         `json:"qty"`
	Discount    *Price      `json:"discount,omitempty"`
	Disclaimer  string      `json:"disclaimer,omitempty"`
	ArticleType string      `json:"type,omitempty"`
	StoreId     *string     `json:"storeId,omitempty"`
	Meta        *ItemMeta   `json:"meta,omitempty"`
	SaleStatus  string      `json:"saleStatus"`
}

type CartDelivery struct {
	Id          uint32                `json:"id"`
	Provider    string                `json:"provider"`
	Price       Price                 `json:"price"`
	Items       []uint32              `json:"items"`
	PickupPoint *messages.PickupPoint `json:"pickupPoint,omitempty"`
}

type CartNotification struct {
	LinkedId int    `json:"id"`
	Provider string `json:"provider"`
	Title    string `json:"title"`
	Content  string `json:"content"`
}

type SubscriptionDetails struct {
	Id           string          `json:"id"`
	OfferingCode string          `json:"offeringCode,omitempty"`
	SigningType  string          `json:"signingType,omitempty"`
	Meta         json.RawMessage `json:"data,omitempty"`
}

type CartGrain struct {
	mu                  sync.RWMutex
	lastItemId          uint32
	lastDeliveryId      uint32
	lastVoucherId       uint32
	lastAccess          time.Time
	lastChange          time.Time // unix seconds of last successful mutation (replay sets from event ts)
	userId              string
	Id                  CartId                          `json:"id"`
	Items               []*CartItem                     `json:"items"`
	TotalPrice          *Price                          `json:"totalPrice"`
	TotalDiscount       *Price                          `json:"totalDiscount"`
	Deliveries          []*CartDelivery                 `json:"deliveries,omitempty"`
	Processing          bool                            `json:"processing"`
	PaymentInProgress   bool                            `json:"paymentInProgress"`
	OrderReference      string                          `json:"orderReference,omitempty"`
	PaymentStatus       string                          `json:"paymentStatus,omitempty"`
	Vouchers            []*Voucher                      `json:"vouchers,omitempty"`
	Notifications       []CartNotification              `json:"cartNotification,omitempty"`
	SubscriptionDetails map[string]*SubscriptionDetails `json:"subscriptionDetails,omitempty"`
}

type Voucher struct {
	Code  string                  `json:"code"`
	Rules []*messages.VoucherRule `json:"rules"`
	Id    uint32                  `json:"id"`
	Value int64                   `json:"value"`
}

func (v *Voucher) AppliesTo(cart *CartGrain) ([]*CartItem, bool) {
	// No rules -> applies to entire cart
	if len(v.Rules) == 0 {
		return cart.Items, true
	}

	// Build evaluation context once
	ctx := voucher.EvalContext{
		Items:        make([]voucher.Item, 0, len(cart.Items)),
		CartTotalInc: 0,
	}

	if cart.TotalPrice != nil {
		ctx.CartTotalInc = cart.TotalPrice.IncVat
	}

	for _, it := range cart.Items {
		category := ""
		if it.Meta != nil {
			category = it.Meta.Category
		}
		ctx.Items = append(ctx.Items, voucher.Item{
			Sku:       it.Sku,
			Category:  category,
			UnitPrice: it.Price.IncVat,
		})
	}

	// All voucher rules must pass (logical AND)
	for _, rule := range v.Rules {
		expr := rule.GetCondition()
		if expr == "" {
			// Empty condition treated as pass (acts like a comment / placeholder)
			continue
		}
		rs, err := voucher.ParseRules(expr)
		if err != nil {
			// Fail closed on parse error
			return nil, false
		}
		if !rs.Applies(ctx) {
			return nil, false
		}
	}

	return cart.Items, true
}

func NewCartGrain(id uint64, ts time.Time) *CartGrain {
	return &CartGrain{
		lastItemId:          0,
		lastDeliveryId:      0,
		lastVoucherId:       0,
		lastAccess:          ts,
		lastChange:          ts,
		TotalDiscount:       NewPrice(),
		Vouchers:            []*Voucher{},
		Deliveries:          []*CartDelivery{},
		Id:                  CartId(id),
		Items:               []*CartItem{},
		TotalPrice:          NewPrice(),
		SubscriptionDetails: make(map[string]*SubscriptionDetails),
	}
}

func (c *CartGrain) GetId() uint64 {
	return uint64(c.Id)
}

func (c *CartGrain) GetLastChange() time.Time {
	return c.lastChange
}

func (c *CartGrain) GetLastAccess() time.Time {
	return c.lastAccess
}

func (c *CartGrain) GetCurrentState() (*CartGrain, error) {
	c.lastAccess = time.Now()
	return c, nil
}

func (c *CartGrain) GetState() ([]byte, error) {
	return json.Marshal(c)
}

func (c *CartGrain) ItemsWithDelivery() []uint32 {
	ret := make([]uint32, 0, len(c.Items))
	for _, item := range c.Items {
		for _, delivery := range c.Deliveries {
			for _, id := range delivery.Items {
				if item.Id == id {
					ret = append(ret, id)
				}
			}
		}
	}
	return ret
}

func (c *CartGrain) ItemsWithoutDelivery() []uint32 {
	ret := make([]uint32, 0, len(c.Items))
	hasDelivery := c.ItemsWithDelivery()
	for _, item := range c.Items {
		found := slices.Contains(hasDelivery, item.Id)

		if !found {
			ret = append(ret, item.Id)
		}
	}
	return ret
}

func (c *CartGrain) FindItemWithSku(sku string) (*CartItem, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	for _, item := range c.Items {
		if item.Sku == sku {
			return item, true
		}
	}
	return nil, false
}

// func (c *CartGrain) Apply(content proto.Message, isReplay bool) (*CartGrain, error) {

// 	updated, err := ApplyRegistered(c, content)
// 	if err != nil {
// 		if err == ErrMutationNotRegistered {
// 			return nil, fmt.Errorf("unsupported mutation type %T (not registered)", content)
// 		}
// 		return nil, err
// 	}

// 	// Sliding TTL: update lastChange only for non-replay successful mutations.
// 	if updated != nil && !isReplay {
// 		c.lastChange = time.Now()
// 		c.lastAccess = time.Now()
// 		go AppendCartEvent(c.Id, content)
// 	}

// 	return updated, nil
// }

func (c *CartGrain) UpdateTotals() {
	c.TotalPrice = NewPrice()
	c.TotalDiscount = NewPrice()

	for _, item := range c.Items {
		rowTotal := MultiplyPrice(item.Price, int64(item.Quantity))

		item.TotalPrice = *rowTotal

		c.TotalPrice.Add(*rowTotal)

		if item.OrgPrice != nil {
			diff := NewPrice()
			diff.Add(*item.OrgPrice)
			diff.Subtract(item.Price)
			if diff.IncVat > 0 {
				c.TotalDiscount.Add(*diff)
			}
		}

	}
	for _, delivery := range c.Deliveries {
		c.TotalPrice.Add(delivery.Price)
	}
	for _, voucher := range c.Vouchers {
		if _, ok := voucher.AppliesTo(c); ok {
			value := NewPriceFromIncVat(voucher.Value, 25)
			if c.TotalPrice.IncVat <= value.IncVat {
				// don't apply discounts to more than the total price
				continue
			}

			c.TotalDiscount.Add(*value)
			c.TotalPrice.Subtract(*value)
		}
	}
}
