"""Core types for pyfortis — frozen dataclasses and enums."""

from __future__ import annotations

import enum
from dataclasses import dataclass, field
from typing import Any


# ── Enums ──────────────────────────────────────────────────────────────────


class Side(enum.Enum):
    BUY = "buy"
    SELL = "sell"


class OrderType(enum.Enum):
    MARKET = "market"
    LIMIT = "limit"
    STOP = "stop"
    STOP_LIMIT = "stop_limit"


class Severity(enum.Enum):
    INFO = "INFO"
    WARNING = "WARNING"
    CRITICAL = "CRITICAL"
    KILL = "KILL"

    @property
    def rank(self) -> int:
        return _SEVERITY_RANK[self]


_SEVERITY_RANK = {
    Severity.INFO: 0,
    Severity.WARNING: 1,
    Severity.CRITICAL: 2,
    Severity.KILL: 3,
}


class LimitScope(enum.Enum):
    ORDER = "order"
    INSTRUMENT = "instrument"
    STRATEGY = "strategy"
    SECTOR = "sector"
    PORTFOLIO = "portfolio"


class LimitType(enum.Enum):
    POSITION = "position"
    CONCENTRATION = "concentration"
    PRICE_TOLERANCE = "price_tolerance"
    ORDER_RATE = "order_rate"
    NOTIONAL = "notional"
    SELF_TRADE = "self_trade"
    CUSTOM = "custom"


class MetricType(enum.Enum):
    VAR = "var"
    CVAR = "cvar"
    DRAWDOWN = "drawdown"
    VOLATILITY = "volatility"
    BETA = "beta"
    SHARPE = "sharpe"
    SORTINO = "sortino"


class VaRMethod(enum.Enum):
    HISTORICAL = "historical"
    PARAMETRIC = "parametric"
    MONTE_CARLO = "monte_carlo"


class BreachAction(enum.Enum):
    LOG = "log"
    NOTIFY = "notify"
    BLOCK_ORDER = "block_order"
    FLATTEN_POSITIONS = "flatten_positions"
    HALT_TRADING = "halt_trading"


class CircuitBreakerType(enum.Enum):
    DAILY_PNL = "daily_pnl"
    DRAWDOWN = "drawdown"
    VOLATILITY = "volatility"


class GateVerdict(enum.Enum):
    APPROVED = "APPROVED"
    REJECTED = "REJECTED"
    APPROVED_WITH_WARNING = "APPROVED_WITH_WARNING"


# ── Value Objects ──────────────────────────────────────────────────────────


@dataclass(frozen=True, slots=True)
class Order:
    order_id: str
    instrument: str
    side: Side
    quantity: float
    price: float
    portfolio: str = "default"
    strategy: str = ""
    sector: str = ""
    order_type: OrderType = OrderType.MARKET
    meta: dict[str, Any] = field(default_factory=dict)

    @property
    def signed_quantity(self) -> float:
        return self.quantity if self.side is Side.BUY else -self.quantity

    @property
    def notional(self) -> float:
        return abs(self.quantity * self.price)


@dataclass(frozen=True, slots=True)
class Position:
    instrument: str
    quantity: float
    avg_price: float
    current_price: float
    portfolio: str = "default"
    sector: str = ""
    meta: dict[str, Any] = field(default_factory=dict)

    @property
    def is_long(self) -> bool:
        return self.quantity > 0

    @property
    def is_short(self) -> bool:
        return self.quantity < 0

    @property
    def notional(self) -> float:
        return abs(self.quantity * self.current_price)

    @property
    def unrealized_pnl(self) -> float:
        return self.quantity * (self.current_price - self.avg_price)


@dataclass(frozen=True, slots=True)
class Exposure:
    instrument: str
    long_notional: float = 0.0
    short_notional: float = 0.0
    net_notional: float = 0.0
    gross_notional: float = 0.0
    sector: str = ""
    portfolio: str = "default"

    @classmethod
    def from_position(cls, pos: Position) -> Exposure:
        notional = pos.notional
        return cls(
            instrument=pos.instrument,
            long_notional=notional if pos.is_long else 0.0,
            short_notional=notional if pos.is_short else 0.0,
            net_notional=pos.quantity * pos.current_price,
            gross_notional=notional,
            sector=pos.sector,
            portfolio=pos.portfolio,
        )


# ── Config Specs ───────────────────────────────────────────────────────────


@dataclass(frozen=True, slots=True)
class LimitSpec:
    name: str
    type: LimitType
    scope: LimitScope
    params: dict[str, Any] = field(default_factory=dict)
    severity: Severity = Severity.WARNING
    enabled: bool = True

    @classmethod
    def from_config(cls, cfg: dict[str, Any]) -> LimitSpec:
        return cls(
            name=cfg["name"],
            type=LimitType(cfg["type"]),
            scope=LimitScope(cfg.get("scope", "order")),
            params=dict(cfg.get("params", {})),
            severity=Severity(cfg.get("severity", "WARNING")),
            enabled=cfg.get("enabled", True),
        )


@dataclass(frozen=True, slots=True)
class MetricSpec:
    name: str
    type: MetricType
    params: dict[str, Any] = field(default_factory=dict)
    severity: Severity | None = None
    enabled: bool = True

    @classmethod
    def from_config(cls, cfg: dict[str, Any]) -> MetricSpec:
        sev_raw = cfg.get("severity")
        return cls(
            name=cfg["name"],
            type=MetricType(cfg["type"]),
            params=dict(cfg.get("params", {})),
            severity=Severity(sev_raw) if sev_raw else None,
            enabled=cfg.get("enabled", True),
        )


@dataclass(frozen=True, slots=True)
class CircuitBreakerSpec:
    name: str
    type: CircuitBreakerType
    params: dict[str, Any] = field(default_factory=dict)
    action: BreachAction = BreachAction.HALT_TRADING
    cooldown: str = "1h"

    @classmethod
    def from_config(cls, cfg: dict[str, Any]) -> CircuitBreakerSpec:
        return cls(
            name=cfg["name"],
            type=CircuitBreakerType(cfg["type"]),
            params=dict(cfg.get("params", {})),
            action=BreachAction(cfg.get("action", "halt_trading")),
            cooldown=cfg.get("cooldown", "1h"),
        )


@dataclass(frozen=True, slots=True)
class EscalationPolicy:
    default_action: BreachAction = BreachAction.LOG
    escalation: dict[Severity, list[BreachAction]] = field(default_factory=dict)

    @classmethod
    def from_config(cls, cfg: dict[str, Any]) -> EscalationPolicy:
        default = BreachAction(cfg.get("default_action", "log"))
        raw_esc = cfg.get("escalation", {})
        escalation: dict[Severity, list[BreachAction]] = {}
        for sev_str, actions in raw_esc.items():
            sev = Severity(sev_str)
            escalation[sev] = [BreachAction(a) for a in actions]
        return cls(default_action=default, escalation=escalation)


# ── Results ────────────────────────────────────────────────────────────────


@dataclass(frozen=True, slots=True)
class LimitCheckResult:
    limit_name: str
    passed: bool
    severity: Severity
    message: str = ""
    details: dict[str, Any] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class MetricResult:
    metric_name: str
    value: float
    breach: bool = False
    severity: Severity | None = None
    message: str = ""
    details: dict[str, Any] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class BreachRecord:
    limit_name: str
    severity: Severity
    message: str
    order_id: str = ""
    instrument: str = ""
    timestamp: str = ""
    actions: list[BreachAction] = field(default_factory=list)
    details: dict[str, Any] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class GateResult:
    verdict: GateVerdict
    order_id: str
    checks: list[LimitCheckResult] = field(default_factory=list)
    breaches: list[BreachRecord] = field(default_factory=list)
    message: str = ""

    @property
    def is_approved(self) -> bool:
        return self.verdict in (GateVerdict.APPROVED, GateVerdict.APPROVED_WITH_WARNING)

    @property
    def is_rejected(self) -> bool:
        return self.verdict is GateVerdict.REJECTED

    @classmethod
    def approved(cls, order_id: str, checks: list[LimitCheckResult] | None = None) -> GateResult:
        return cls(
            verdict=GateVerdict.APPROVED,
            order_id=order_id,
            checks=checks or [],
            message="Order approved",
        )

    @classmethod
    def rejected(
        cls,
        order_id: str,
        checks: list[LimitCheckResult] | None = None,
        breaches: list[BreachRecord] | None = None,
        message: str = "Order rejected",
    ) -> GateResult:
        return cls(
            verdict=GateVerdict.REJECTED,
            order_id=order_id,
            checks=checks or [],
            breaches=breaches or [],
            message=message,
        )

    @classmethod
    def approved_with_warning(
        cls,
        order_id: str,
        checks: list[LimitCheckResult] | None = None,
        breaches: list[BreachRecord] | None = None,
        message: str = "Order approved with warnings",
    ) -> GateResult:
        return cls(
            verdict=GateVerdict.APPROVED_WITH_WARNING,
            order_id=order_id,
            checks=checks or [],
            breaches=breaches or [],
            message=message,
        )
