from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Literal


def _f(v: Any) -> float:
    try:
        return float(v)
    except Exception:
        return 0.0


def _i(v: Any) -> int:
    try:
        return int(v)
    except Exception:
        return 0


def _normalize_side(fill: dict[str, Any]) -> str:
    """
    Normalize side using WS payload `dir` field (ignore `side`):
    - open long  -> Buy
    - open short -> Sell
    - close long -> Sell
    - close short -> Buy
    """
    dir_raw = str(fill.get("dir", "") or "").strip().lower()
    if not dir_raw:
        return ""

    # best-effort inference:
    # - open long  -> Buy
    # - open short -> Sell
    # - close long -> Sell
    # - close short -> Buy
    is_open = "open" in dir_raw
    is_close = "close" in dir_raw
    is_long = "long" in dir_raw
    is_short = "short" in dir_raw
    if is_open and is_long:
        return "Buy"
    if is_open and is_short:
        return "Sell"
    if is_close and is_long:
        return "Sell"
    if is_close and is_short:
        return "Buy"
    return str(fill.get("dir", "") or "")


@dataclass(frozen=True)
class FillEvent:
    user: str
    coin: str
    side: str
    px: float
    sz: float
    time_ms: int
    tid: int | None
    oid: int | None
    closed_pnl: float
    fee: float
    raw: dict[str, Any]

    @property
    def notional_usd(self) -> float:
        return abs(self.px * self.sz)

    @staticmethod
    def from_ws_fill(user: str, fill: dict[str, Any]) -> "FillEvent":
        return FillEvent(
            user=user,
            coin=str(fill.get("coin", "")),
            side=_normalize_side(fill),
            px=_f(fill.get("px")),
            sz=_f(fill.get("sz")),
            time_ms=_i(fill.get("time")),
            tid=_i(fill.get("tid")) if fill.get("tid") is not None else None,
            oid=_i(fill.get("oid")) if fill.get("oid") is not None else None,
            closed_pnl=_f(fill.get("closedPnl")),
            fee=_f(fill.get("fee")),
            raw=fill,
        )


UserEventKind = Literal["fills", "funding", "liquidation", "nonUserCancel", "unknown"]


@dataclass(frozen=True)
class UserEvent:
    user: str
    kind: UserEventKind
    raw: dict[str, Any]

    @staticmethod
    def from_ws(user: str, data: Any) -> "UserEvent":
        if isinstance(data, dict):
            for k in ("fills", "funding", "liquidation", "nonUserCancel"):
                if k in data:
                    return UserEvent(user=user, kind=k, raw=data)  # type: ignore[arg-type]
        return UserEvent(user=user, kind="unknown", raw={"data": data})

