"""Exchange data models for OpenClaw trading system.

This module defines Pydantic models for exchange-related data structures
including orders, balances, positions, and market data.
"""

from datetime import datetime
from enum import Enum
from typing import Optional

from pydantic import BaseModel, Field, field_validator


class OrderSide(str, Enum):
    """Order side (buy or sell)."""

    BUY = "buy"
    SELL = "sell"


class OrderType(str, Enum):
    """Order type."""

    MARKET = "market"
    LIMIT = "limit"
    STOP_LOSS = "stop_loss"
    TAKE_PROFIT = "take_profit"


class OrderStatus(str, Enum):
    """Order execution status."""

    PENDING = "pending"
    OPEN = "open"
    PARTIALLY_FILLED = "partially_filled"
    FILLED = "filled"
    CANCELLED = "cancelled"
    REJECTED = "rejected"
    EXPIRED = "expired"


class Order(BaseModel):
    """Trading order representation.

    Attributes:
        order_id: Unique order identifier
        symbol: Trading pair/symbol (e.g., "BTC/USDT", "AAPL")
        side: Buy or sell
        order_type: Market, limit, stop_loss, etc.
        amount: Order quantity (base asset)
        price: Order price (for limit orders)
        status: Current order status
        filled_amount: Amount that has been filled
        created_at: Order creation timestamp
        updated_at: Last update timestamp
        exchange_id: Exchange-specific order ID
        client_order_id: Client-provided order ID
    """

    order_id: str = Field(..., description="Unique order identifier")
    symbol: str = Field(..., description="Trading symbol (e.g., BTC/USDT)")
    side: OrderSide = Field(..., description="Buy or sell")
    order_type: OrderType = Field(default=OrderType.MARKET, description="Order type")
    amount: float = Field(..., gt=0, description="Order quantity")
    price: Optional[float] = Field(default=None, ge=0, description="Order price")
    status: OrderStatus = Field(default=OrderStatus.PENDING, description="Order status")
    filled_amount: float = Field(default=0.0, ge=0, description="Filled quantity")
    created_at: datetime = Field(default_factory=datetime.now, description="Creation time")
    updated_at: datetime = Field(default_factory=datetime.now, description="Last update time")
    exchange_id: Optional[str] = Field(default=None, description="Exchange order ID")
    client_order_id: Optional[str] = Field(default=None, description="Client order ID")

    @field_validator("updated_at", mode="before")
    @classmethod
    def set_updated_at(cls, v: Optional[datetime], info) -> datetime:
        """Ensure updated_at is set."""
        return v or datetime.now()

    @property
    def is_filled(self) -> bool:
        """Check if order is completely filled."""
        return self.status == OrderStatus.FILLED or self.filled_amount >= self.amount

    @property
    def remaining_amount(self) -> float:
        """Get remaining amount to be filled."""
        return max(0.0, self.amount - self.filled_amount)

    @property
    def fill_percentage(self) -> float:
        """Get fill percentage (0.0 to 100.0)."""
        if self.amount <= 0:
            return 0.0
        return min(100.0, (self.filled_amount / self.amount) * 100)


class Balance(BaseModel):
    """Account balance for a single asset.

    Attributes:
        asset: Asset symbol (e.g., "BTC", "USDT", "USD")
        free: Available/free balance
        locked: Locked/held balance (in orders, etc.)
        total: Total balance (free + locked)
    """

    asset: str = Field(..., description="Asset symbol")
    free: float = Field(default=0.0, ge=0, description="Available balance")
    locked: float = Field(default=0.0, ge=0, description="Locked balance")

    @property
    def total(self) -> float:
        """Calculate total balance."""
        return self.free + self.locked


class Position(BaseModel):
    """Trading position for a symbol.

    Attributes:
        symbol: Trading symbol
        side: Long or short (buy=long, sell=short)
        amount: Position size
        entry_price: Average entry price
        current_price: Current market price
        unrealized_pnl: Unrealized profit/loss
        leverage: Leverage used (1.0 = no leverage)
    """

    symbol: str = Field(..., description="Trading symbol")
    side: OrderSide = Field(..., description="Position side (buy=long, sell=short)")
    amount: float = Field(..., gt=0, description="Position size")
    entry_price: float = Field(..., gt=0, description="Average entry price")
    current_price: Optional[float] = Field(default=None, ge=0, description="Current price")
    leverage: float = Field(default=1.0, gt=0, description="Leverage multiplier")

    @property
    def unrealized_pnl(self) -> float:
        """Calculate unrealized PnL."""
        if self.current_price is None:
            return 0.0

        price_diff = self.current_price - self.entry_price
        if self.side == OrderSide.SELL:  # Short position
            price_diff = -price_diff

        return price_diff * self.amount * self.leverage

    @property
    def unrealized_pnl_pct(self) -> float:
        """Calculate unrealized PnL percentage."""
        if self.entry_price <= 0:
            return 0.0
        return (self.unrealized_pnl / (self.entry_price * self.amount)) * 100

    @property
    def market_value(self) -> float:
        """Calculate current market value of position."""
        price = self.current_price or self.entry_price
        return price * self.amount


class Ticker(BaseModel):
    """Market ticker data for a symbol.

    Attributes:
        symbol: Trading symbol
        bid: Best bid price
        ask: Best ask price
        last: Last traded price
        high: 24h high price
        low: 24h low price
        volume: 24h trading volume
        timestamp: Data timestamp
    """

    symbol: str = Field(..., description="Trading symbol")
    bid: float = Field(..., gt=0, description="Best bid price")
    ask: float = Field(..., gt=0, description="Best ask price")
    last: float = Field(..., gt=0, description="Last traded price")
    high: Optional[float] = Field(default=None, ge=0, description="24h high")
    low: Optional[float] = Field(default=None, ge=0, description="24h low")
    volume: Optional[float] = Field(default=None, ge=0, description="24h volume")
    timestamp: datetime = Field(default_factory=datetime.now, description="Timestamp")

    @property
    def spread(self) -> float:
        """Calculate bid-ask spread."""
        return self.ask - self.bid

    @property
    def spread_pct(self) -> float:
        """Calculate bid-ask spread percentage."""
        if self.last <= 0:
            return 0.0
        return (self.spread / self.last) * 100

    @property
    def mid_price(self) -> float:
        """Calculate mid price."""
        return (self.bid + self.ask) / 2
