from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal

from .models import AccountSnapshot, OrderSide, RiskDecision, Signal, SignalType, SymbolMetadata, TradingMode


@dataclass(frozen=True)
class RiskSettings:
    mode: TradingMode | str = TradingMode.DRY_RUN
    max_quote_amount: Decimal = Decimal("50")
    cooldown_seconds: int = 60
    prevent_repeated_side: bool = True

    def __post_init__(self) -> None:
        if self.max_quote_amount <= 0:
            raise ValueError("max_quote_amount must be positive")
        if self.cooldown_seconds < 0:
            raise ValueError("cooldown_seconds must be non-negative")


class RiskEngine:
    def __init__(self, settings: RiskSettings) -> None:
        self.settings = settings
        self._last_trade_ms: int | None = None
        self._last_side: OrderSide | None = None

    def evaluate(
        self,
        signal: Signal,
        metadata: SymbolMetadata,
        account: AccountSnapshot,
        quote_amount: Decimal,
        now_ms: int,
        last_price: Decimal | None = None,
    ) -> RiskDecision:
        if self.settings.mode not in {TradingMode.DRY_RUN, TradingMode.TESTNET_LIVE, TradingMode.LIVE}:
            return RiskDecision(False, "unsupported trading mode", signal)
        if signal.signal_type is SignalType.HOLD or signal.side is None:
            return RiskDecision(False, "hold signal", signal)
        if signal.symbol != metadata.symbol:
            return RiskDecision(False, "unsupported symbol", signal)
        if quote_amount > self.settings.max_quote_amount:
            return RiskDecision(False, "quote amount above max", signal)
        if quote_amount < metadata.min_notional:
            return RiskDecision(False, "quote amount below min notional", signal)
        if self.settings.prevent_repeated_side and self._last_side is signal.side:
            return RiskDecision(False, "duplicate same-side signal", signal)
        if self._last_trade_ms is not None:
            elapsed_ms = now_ms - self._last_trade_ms
            if elapsed_ms < self.settings.cooldown_seconds * 1000:
                return RiskDecision(False, "cooldown active", signal)
        if signal.side is OrderSide.BUY and account.free(metadata.quote_asset) < quote_amount:
            return RiskDecision(False, "insufficient quote balance", signal)
        if signal.side is OrderSide.SELL:
            base_value = account.free(metadata.base_asset) * last_price if last_price is not None else Decimal("0")
            if base_value < quote_amount:
                return RiskDecision(False, "insufficient base balance", signal)

        self._last_trade_ms = now_ms
        self._last_side = signal.side
        return RiskDecision(True, "approved", signal, quote_amount)
