"""Market data models for ZenOTC SDK."""

from __future__ import annotations

from datetime import datetime
from decimal import Decimal
from typing import List, Optional

from pydantic import Field

from zenotc.models.base import BaseModel


class Price(BaseModel):
    """Current price for an asset."""

    asset: str = Field(..., description="Asset symbol")
    bid: Decimal = Field(..., description="Best bid price")
    ask: Decimal = Field(..., description="Best ask price")
    mid: Decimal = Field(..., description="Mid price")
    last: Optional[Decimal] = Field(None, description="Last trade price")
    volume_24h: Optional[Decimal] = Field(None, description="24-hour volume")
    change_24h: Optional[Decimal] = Field(None, description="24-hour change percentage")
    high_24h: Optional[Decimal] = Field(None, description="24-hour high")
    low_24h: Optional[Decimal] = Field(None, description="24-hour low")
    timestamp: datetime = Field(..., description="Price timestamp")

    @property
    def spread(self) -> Decimal:
        """Get bid-ask spread."""
        return self.ask - self.bid

    @property
    def spread_bps(self) -> float:
        """Get spread in basis points."""
        if self.mid == 0:
            return 0.0
        return float(self.spread / self.mid * 10000)


class PriceUpdate(BaseModel):
    """Real-time price update from WebSocket."""

    asset: str = Field(..., description="Asset symbol")
    bid: Decimal = Field(..., description="Best bid price")
    ask: Decimal = Field(..., description="Best ask price")
    mid: Decimal = Field(..., description="Mid price")
    last: Optional[Decimal] = Field(None, description="Last trade price")
    timestamp: datetime = Field(..., description="Update timestamp")
    sequence: int = Field(..., description="Sequence number for ordering")


class OrderBookLevel(BaseModel):
    """Single level in the order book."""

    price: Decimal = Field(..., description="Price level")
    quantity: Decimal = Field(..., description="Total quantity at this level")
    order_count: int = Field(default=1, description="Number of orders at this level")


class OrderBook(BaseModel):
    """Order book snapshot."""

    asset: str = Field(..., description="Asset symbol")
    bids: List[OrderBookLevel] = Field(..., description="Bid levels (descending by price)")
    asks: List[OrderBookLevel] = Field(..., description="Ask levels (ascending by price)")
    timestamp: datetime = Field(..., description="Snapshot timestamp")
    sequence: Optional[int] = Field(None, description="Sequence number")

    @property
    def best_bid(self) -> Optional[OrderBookLevel]:
        """Get best bid level."""
        return self.bids[0] if self.bids else None

    @property
    def best_ask(self) -> Optional[OrderBookLevel]:
        """Get best ask level."""
        return self.asks[0] if self.asks else None

    @property
    def mid_price(self) -> Optional[Decimal]:
        """Get mid price."""
        if self.best_bid and self.best_ask:
            return (self.best_bid.price + self.best_ask.price) / 2
        return None

    @property
    def spread(self) -> Optional[Decimal]:
        """Get bid-ask spread."""
        if self.best_bid and self.best_ask:
            return self.best_ask.price - self.best_bid.price
        return None

    def get_bid_depth(self, levels: int = 10) -> Decimal:
        """Get total bid depth up to specified levels."""
        return sum(level.quantity for level in self.bids[:levels])

    def get_ask_depth(self, levels: int = 10) -> Decimal:
        """Get total ask depth up to specified levels."""
        return sum(level.quantity for level in self.asks[:levels])

    def get_price_for_quantity(self, side: str, quantity: Decimal) -> Optional[Decimal]:
        """
        Calculate average price to fill a given quantity.

        Args:
            side: 'buy' or 'sell'
            quantity: Quantity to fill

        Returns:
            Average execution price, or None if insufficient liquidity
        """
        levels = self.asks if side == "buy" else self.bids
        remaining = quantity
        total_cost = Decimal("0")

        for level in levels:
            if remaining <= 0:
                break
            fill_qty = min(remaining, level.quantity)
            total_cost += fill_qty * level.price
            remaining -= fill_qty

        if remaining > 0:
            return None  # Insufficient liquidity

        return total_cost / quantity
