from enum import StrEnum
from typing import Annotated

from pydantic import BaseModel, Field, BeforeValidator, PlainSerializer


# ---------------------------------------------------------------------------
# Scalar coercion helpers
# ---------------------------------------------------------------------------

def _to_int(v: int | str) -> int:
    return int(v, 0) if isinstance(v, str) else int(v)


def _to_float(v: float | int | str) -> float:
    return float(v)


_int_to_str = PlainSerializer(str, return_type=str, when_used="json")
_float_to_str = PlainSerializer(str, return_type=str, when_used="json")

# Always stored as Python int; accepts int or string on input; serialises to string in JSON.
type Uint8   = Annotated[int, BeforeValidator(_to_int), _int_to_str]
type Uint16  = Annotated[int, BeforeValidator(_to_int), _int_to_str]
type Uint24  = Annotated[int, BeforeValidator(_to_int), _int_to_str]
type Uint32  = Annotated[int, BeforeValidator(_to_int), _int_to_str]
type Uint64  = Annotated[int, BeforeValidator(_to_int), _int_to_str]
type Uint256 = Annotated[int, BeforeValidator(_to_int), _int_to_str]
type Float   = Annotated[float, BeforeValidator(_to_float), _float_to_str]

ETH_ADDRESS_PATTERN = r"^0x[0-9a-fA-F]{40}$"


# ---------------------------------------------------------------------------
# Enums
# ---------------------------------------------------------------------------

class Exchange(StrEnum):
    UNISWAP_V2 = "UniswapV2"
    UNISWAP_V3 = "UniswapV3"


class Side(StrEnum):
    """Order side: buy or sell"""
    BUY = "BUY"
    SELL = "SELL"


class AmountType(StrEnum):
    """Whether the order amount refers to base or quote currency"""
    BASE = "BASE"  # Amount is in base currency (e.g., BTC in BTC/USD)
    QUOTE = "QUOTE"  # Amount is in quote currency (e.g., USD in BTC/USD)


class TimeInForce(StrEnum):
    """Order lifetime specification"""
    GTC = "GTC"  # Good Till Cancel
    IOC = "IOC"  # Immediate or Cancel
    FOK = "FOK"  # Fill or Kill
    DAY = "DAY"  # Good for trading day
    GTD = "GTD"  # Good Till Date


class ConditionalOrderMode(StrEnum):
    """How conditional orders behave on partial fills"""
    NEW_PER_FILL = "NEW_PER_FILL"  # Create new conditional order per each fill
    UNIFIED_ADJUSTING = "UNIFIED_ADJUSTING"  # Single conditional order that adjusts amount


class TriggerType(StrEnum):
    """Type of conditional trigger"""
    STOP_LOSS = "STOP_LOSS"
    TAKE_PROFIT = "TAKE_PROFIT"
    STOP_LIMIT = "STOP_LIMIT"
    TRAILING_STOP = "TRAILING_STOP"


class TickSpacingMode(StrEnum):
    """How price tick spacing is determined"""
    FIXED = "FIXED"  # Fixed tick size
    DYNAMIC = "DYNAMIC"  # Tick size varies by price level
    CONTINUOUS = "CONTINUOUS"  # No tick restrictions


class AssetType(StrEnum):
    """Type of tradeable asset"""
    SPOT = "SPOT"  # Spot/cash market
    MARGIN = "MARGIN"  # Margin trading
    PERP = "PERP"  # Perpetual futures
    FUTURE = "FUTURE"  # Dated futures
    OPTION = "OPTION"  # Options
    SYNTHETIC = "SYNTHETIC"  # Synthetic/derived instruments


class OcoMode(StrEnum):
    NO_OCO = "NO_OCO"
    CANCEL_ON_PARTIAL_FILL = "CANCEL_ON_PARTIAL_FILL"
    CANCEL_ON_COMPLETION = "CANCEL_ON_COMPLETION"


# ---------------------------------------------------------------------------
# Supporting models
# ---------------------------------------------------------------------------

class Route(BaseModel):
    model_config = {"extra": "forbid"}

    exchange: Exchange
    fee: Uint24 = Field(description="Pool fee tier; also used as maxFee on UniswapV3")


class Line(BaseModel):
    """Price line: price = intercept + slope * time. Both zero means line is disabled."""

    model_config = {"extra": "forbid"}

    intercept: Float
    slope: Float


class Tranche(BaseModel):
    model_config = {"extra": "forbid"}

    fraction: Uint16 = Field(description="Fraction of total order amount; MAX_FRACTION (65535) = 100%")
    startTimeIsRelative: bool
    endTimeIsRelative: bool
    minIsBarrier: bool = Field(description="Not yet supported")
    maxIsBarrier: bool = Field(description="Not yet supported")
    marketOrder: bool = Field(
        description="If true, min/max lines ignored; minLine intercept treated as max slippage"
    )
    minIsRatio: bool
    maxIsRatio: bool
    rateLimitFraction: Uint16 = Field(description="Max fraction of this tranche's amount per rate-limited execution")
    rateLimitPeriod: Uint24 = Field(description="Seconds between rate limit resets")
    startTime: Uint32 = Field(description="Unix timestamp; 0 (DISTANT_PAST) effectively disables")
    endTime: Uint32 = Field(description="Unix timestamp; 4294967295 (DISTANT_FUTURE) effectively disables")
    minLine: Line = Field(description="Traditional limit order constraint; can be diagonal")
    maxLine: Line = Field(description="Upper price boundary (too-good-a-price guard)")


class TrancheStatus(BaseModel):
    model_config = {"extra": "forbid"}

    filled: Uint256 = Field(description="Amount filled by this tranche")
    activationTime: Uint32 = Field(description="Earliest time this tranche can execute; 0 = not yet concrete")
    startTime: Uint32 = Field(description="Concrete start timestamp")
    endTime: Uint32 = Field(description="Concrete end timestamp")


# ---------------------------------------------------------------------------
# Standard Order Models
# ---------------------------------------------------------------------------

class ConditionalTrigger(BaseModel):
    """Conditional order trigger (stop-loss, take-profit, etc.)"""

    model_config = {"extra": "forbid"}

    trigger_type: TriggerType
    trigger_price: Float = Field(description="Price at which conditional order activates")
    trailing_delta: Float | None = Field(default=None, description="For trailing stops: delta from peak/trough")


class AmountConstraints(BaseModel):
    """Constraints on order amounts for a symbol"""

    model_config = {"extra": "forbid"}

    min_amount: Float = Field(description="Minimum order amount")
    max_amount: Float = Field(description="Maximum order amount")
    step_size: Float = Field(description="Amount increment granularity")


class PriceConstraints(BaseModel):
    """Constraints on order pricing for a symbol"""

    model_config = {"extra": "forbid"}

    tick_spacing_mode: TickSpacingMode
    tick_size: Float | None = Field(default=None, description="Fixed tick size (if FIXED mode)")
    min_price: Float | None = Field(default=None, description="Minimum allowed price")
    max_price: Float | None = Field(default=None, description="Maximum allowed price")


class MarketCapabilities(BaseModel):
    """Describes what order features a market supports"""

    model_config = {"extra": "forbid"}

    supported_sides: list[Side] = Field(description="Supported order sides (usually both)")
    supported_amount_types: list[AmountType] = Field(description="Whether BASE, QUOTE, or both amounts are supported")
    supports_market_orders: bool = Field(description="Whether market orders are supported")
    supports_limit_orders: bool = Field(description="Whether limit orders are supported")
    supported_time_in_force: list[TimeInForce] = Field(description="Supported order lifetimes")
    supports_conditional_orders: bool = Field(description="Whether stop-loss/take-profit are supported")
    supported_trigger_types: list[TriggerType] = Field(default_factory=list, description="Supported trigger types")
    supports_post_only: bool = Field(default=False, description="Whether post-only orders are supported")
    supports_reduce_only: bool = Field(default=False, description="Whether reduce-only orders are supported")
    supports_iceberg: bool = Field(default=False, description="Whether iceberg orders are supported")
    market_order_amount_type: AmountType | None = Field(
        default=None,
        description="Required amount type for market orders (some DEXs require exact-in)"
    )


class SymbolMetadata(BaseModel):
    """Complete metadata describing a tradeable symbol/market"""

    model_config = {"extra": "forbid"}

    symbol_id: str = Field(description="Unique symbol identifier")
    base_asset: str = Field(description="Base asset (e.g., 'BTC')")
    quote_asset: str = Field(description="Quote asset (e.g., 'USD')")
    asset_type: AssetType = Field(description="Type of market")
    exchange: str = Field(description="Exchange identifier")

    amount_constraints: AmountConstraints
    price_constraints: PriceConstraints
    capabilities: MarketCapabilities

    contract_size: Float | None = Field(default=None, description="For futures/options: contract multiplier")
    settlement_asset: str | None = Field(default=None, description="For derivatives: settlement currency")
    expiry_timestamp: Uint64 | None = Field(default=None, description="For dated futures/options: expiration")


class StandardOrder(BaseModel):
    """Standard order specification for exchange kernels"""

    model_config = {"extra": "forbid"}

    symbol_id: str = Field(description="Symbol to trade")
    side: Side = Field(description="Buy or sell")
    amount: Float = Field(description="Order amount")
    amount_type: AmountType = Field(description="Whether amount is BASE or QUOTE currency")

    limit_price: Float | None = Field(default=None, description="Limit price (None = market order)")
    time_in_force: TimeInForce = Field(default=TimeInForce.GTC, description="Order lifetime")
    good_till_date: Uint64 | None = Field(default=None, description="Expiry timestamp for GTD orders")

    conditional_trigger: ConditionalTrigger | None = Field(
        default=None,
        description="Stop-loss/take-profit trigger"
    )
    conditional_mode: ConditionalOrderMode | None = Field(
        default=None,
        description="How conditional orders behave on partial fills"
    )

    reduce_only: bool = Field(default=False, description="Only reduce existing position")
    post_only: bool = Field(default=False, description="Only make, never take")
    iceberg_qty: Float | None = Field(default=None, description="Visible amount for iceberg orders")

    client_order_id: str | None = Field(default=None, description="Client-specified order ID")


class StandardOrderStatus(BaseModel):
    """Current status of a standard order"""

    model_config = {"extra": "forbid"}

    order: StandardOrder
    order_id: str = Field(description="Exchange-assigned order ID")
    status: str = Field(description="Order status: NEW, PARTIALLY_FILLED, FILLED, CANCELED, REJECTED, EXPIRED")
    filled_amount: Float = Field(description="Amount filled so far")
    average_fill_price: Float = Field(description="Average execution price")
    created_at: Uint64 = Field(description="Order creation timestamp")
    updated_at: Uint64 = Field(description="Last update timestamp")


# ---------------------------------------------------------------------------
# Order models
# ---------------------------------------------------------------------------

class SwapOrder(BaseModel):
    model_config = {"extra": "forbid"}

    tokenIn: str = Field(pattern=ETH_ADDRESS_PATTERN, description="ERC-20 input token address")
    tokenOut: str = Field(pattern=ETH_ADDRESS_PATTERN, description="ERC-20 output token address")
    route: Route
    amount: Uint256 = Field(description="Maximum quantity to fill")
    minFillAmount: Uint256 = Field(description="Minimum tranche amount before tranche is considered complete")
    amountIsInput: bool = Field(description="true = amount is tokenIn quantity; false = tokenOut")
    outputDirectlyToOwner: bool = Field(description="true = proceeds go to vault owner; false = vault")
    inverted: bool = Field(description="false = tokenIn/tokenOut price direction (Uniswap natural)")
    conditionalOrder: Uint64 = Field(
        description="NO_CONDITIONAL_ORDER = 2^64-1; high bit set = relative index within placement group"
    )
    tranches: list[Tranche] = Field(min_length=1)


class StandardOrderGroup(BaseModel):
    """Group of orders with OCO (One-Cancels-Other) relationship"""

    model_config = {"extra": "forbid"}

    mode: OcoMode
    orders: list[StandardOrder] = Field(min_length=1)


# ---------------------------------------------------------------------------
# Legacy swap order models (kept for backward compatibility)
# ---------------------------------------------------------------------------

class OcoGroup(BaseModel):
    """DEPRECATED: Use StandardOrderGroup instead"""

    model_config = {"extra": "forbid"}

    mode: OcoMode
    orders: list[SwapOrder] = Field(min_length=1)


class SwapOrderStatus(BaseModel):
    model_config = {"extra": "forbid"}

    order: SwapOrder
    fillFeeHalfBps: Uint8 = Field(description="Fill fee in half-bps (1/20000); max 255 = 1.275%")
    canceled: bool = Field(description="If true, order is canceled regardless of cancelAllIndex")
    startTime: Uint32 = Field(description="Earliest block.timestamp at which order may execute")
    ocoGroup: Uint64 = Field(description="Index into ocoGroups; NO_OCO_INDEX = 2^64-1")
    originalOrder: Uint64 = Field(description="Index of the original order in the orders array")
    startPrice: Uint256 = Field(description="Price at order start")
    filled: Uint256 = Field(description="Total amount filled so far")
    trancheStatus: list[TrancheStatus]


