package entity

import (
	"sort"
	"sync"

	"github.com/genex/trading-service/internal/domain/vo"
)

// PriceLevel represents a group of orders at the same price level.
type PriceLevel struct {
	Price  vo.Price `json:"price"`
	Orders []*Order `json:"orders"`
}

// TotalQuantity returns the sum of remaining quantities at this price level.
func (pl *PriceLevel) TotalQuantity() int {
	total := 0
	for _, o := range pl.Orders {
		total += o.RemainingQty.Int()
	}
	return total
}

// OrderCount returns the number of orders at this price level.
func (pl *PriceLevel) OrderCount() int {
	return len(pl.Orders)
}

// OrderBook is a domain entity representing the order book for a single coupon.
// It maintains bid and ask price levels sorted for price-time priority matching.
type OrderBook struct {
	CouponID string       `json:"couponId"`
	Bids     []PriceLevel `json:"bids"` // sorted descending (highest bid first)
	Asks     []PriceLevel `json:"asks"` // sorted ascending (lowest ask first)
	mu       sync.RWMutex
}

// NewOrderBook creates an empty order book for a given coupon.
func NewOrderBook(couponID string) *OrderBook {
	return &OrderBook{CouponID: couponID}
}

// AddOrder inserts an order into the appropriate side of the order book.
func (ob *OrderBook) AddOrder(order *Order) {
	ob.mu.Lock()
	defer ob.mu.Unlock()

	if order.Side == vo.Buy {
		ob.addToPriceLevels(&ob.Bids, order, true)
	} else {
		ob.addToPriceLevels(&ob.Asks, order, false)
	}
}

// RemoveOrder removes an order from the book by ID and side. Returns true if found.
func (ob *OrderBook) RemoveOrder(orderID string, side vo.OrderSide) bool {
	ob.mu.Lock()
	defer ob.mu.Unlock()

	levels := &ob.Bids
	if side == vo.Sell {
		levels = &ob.Asks
	}

	for i, level := range *levels {
		for j, o := range level.Orders {
			if o.ID == orderID {
				level.Orders = append(level.Orders[:j], level.Orders[j+1:]...)
				if len(level.Orders) == 0 {
					*levels = append((*levels)[:i], (*levels)[i+1:]...)
				} else {
					(*levels)[i] = level
				}
				return true
			}
		}
	}
	return false
}

// BestBid returns the highest bid price level, or nil if no bids exist.
func (ob *OrderBook) BestBid() *PriceLevel {
	ob.mu.RLock()
	defer ob.mu.RUnlock()
	if len(ob.Bids) == 0 {
		return nil
	}
	return &ob.Bids[0]
}

// BestAsk returns the lowest ask price level, or nil if no asks exist.
func (ob *OrderBook) BestAsk() *PriceLevel {
	ob.mu.RLock()
	defer ob.mu.RUnlock()
	if len(ob.Asks) == 0 {
		return nil
	}
	return &ob.Asks[0]
}

// Snapshot returns a copy of up to `depth` price levels from each side.
func (ob *OrderBook) Snapshot(depth int) (bids []PriceLevel, asks []PriceLevel) {
	ob.mu.RLock()
	defer ob.mu.RUnlock()

	bidDepth := minInt(depth, len(ob.Bids))
	askDepth := minInt(depth, len(ob.Asks))

	bids = make([]PriceLevel, bidDepth)
	copy(bids, ob.Bids[:bidDepth])
	asks = make([]PriceLevel, askDepth)
	copy(asks, ob.Asks[:askDepth])
	return
}

// MatchBuyOrder attempts to match a buy order against the ask side.
// Returns the list of trades created and modifies the order book in place.
// The caller must hold no lock; this method acquires the write lock internally.
func (ob *OrderBook) MatchBuyOrder(buyOrder *Order, createTrade func(buy, sell *Order, price vo.Price, qty int) *Trade) []*Trade {
	ob.mu.Lock()
	defer ob.mu.Unlock()

	var trades []*Trade
	for len(ob.Asks) > 0 && buyOrder.RemainingQty.IsPositive() {
		bestAsk := &ob.Asks[0]

		// For limit orders, stop if the ask price exceeds our limit
		if buyOrder.Type == vo.Limit && bestAsk.Price.GreaterThan(buyOrder.Price) {
			break
		}

		for len(bestAsk.Orders) > 0 && buyOrder.RemainingQty.IsPositive() {
			sellOrder := bestAsk.Orders[0]
			matchQty := buyOrder.RemainingQty.Min(sellOrder.RemainingQty)
			matchPrice := sellOrder.Price

			trade := createTrade(buyOrder, sellOrder, matchPrice, matchQty.Int())
			trades = append(trades, trade)

			buyOrder.Fill(matchQty)
			sellOrder.Fill(matchQty)

			if sellOrder.RemainingQty.IsZero() {
				bestAsk.Orders = bestAsk.Orders[1:]
			}
		}

		if len(bestAsk.Orders) == 0 {
			ob.Asks = ob.Asks[1:]
		}
	}

	return trades
}

// MatchSellOrder attempts to match a sell order against the bid side.
// Returns the list of trades created and modifies the order book in place.
func (ob *OrderBook) MatchSellOrder(sellOrder *Order, createTrade func(buy, sell *Order, price vo.Price, qty int) *Trade) []*Trade {
	ob.mu.Lock()
	defer ob.mu.Unlock()

	var trades []*Trade
	for len(ob.Bids) > 0 && sellOrder.RemainingQty.IsPositive() {
		bestBid := &ob.Bids[0]

		// For limit orders, stop if the bid price is below our limit
		if sellOrder.Type == vo.Limit && bestBid.Price.LessThan(sellOrder.Price) {
			break
		}

		for len(bestBid.Orders) > 0 && sellOrder.RemainingQty.IsPositive() {
			buyOrder := bestBid.Orders[0]
			matchQty := sellOrder.RemainingQty.Min(buyOrder.RemainingQty)
			matchPrice := buyOrder.Price

			trade := createTrade(buyOrder, sellOrder, matchPrice, matchQty.Int())
			trades = append(trades, trade)

			sellOrder.Fill(matchQty)
			buyOrder.Fill(matchQty)

			if buyOrder.RemainingQty.IsZero() {
				bestBid.Orders = bestBid.Orders[1:]
			}
		}

		if len(bestBid.Orders) == 0 {
			ob.Bids = ob.Bids[1:]
		}
	}

	return trades
}

// addToPriceLevels inserts an order into the correct price level,
// creating a new level if needed. Maintains sort order.
func (ob *OrderBook) addToPriceLevels(levels *[]PriceLevel, order *Order, descending bool) {
	for i, level := range *levels {
		if level.Price.Equal(order.Price) {
			(*levels)[i].Orders = append((*levels)[i].Orders, order)
			return
		}
	}
	*levels = append(*levels, PriceLevel{Price: order.Price, Orders: []*Order{order}})
	sort.Slice(*levels, func(i, j int) bool {
		if descending {
			return (*levels)[i].Price.GreaterThan((*levels)[j].Price)
		}
		return (*levels)[i].Price.LessThan((*levels)[j].Price)
	})
}

func minInt(a, b int) int {
	if a < b {
		return a
	}
	return b
}
