from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from hl1m_cli.copytrade._shared.events import FillEvent


@dataclass(frozen=True)
class RiskProfile:
    scale: float = 1.0
    max_notional_usd: float | None = None
    max_leverage: float | None = None

    @staticmethod
    def _extract_leverage_from_fill(fill: FillEvent) -> float | None:
        raw = fill.raw
        if not isinstance(raw, dict):
            return None

        # Possible field shapes (best-effort across WS payload / SDK versions)
        candidates = [
            "leverage",
            "leverageValue",
            "leverageMultiplier",
        ]
        for k in candidates:
            if k in raw:
                v = raw.get(k)
                try:
                    # "leverage" may be a dict (e.g. {"type":"cross","value":20}); don't swallow it here
                    if isinstance(v, dict):
                        break
                    return float(v)
                except Exception:
                    return None

        # leverage may be a dict, e.g. {"type":"cross","value":20}
        lev = raw.get("leverage")
        if isinstance(lev, dict):
            # value field
            for kk in ("value", "leverage", "multiplier"):
                if kk in lev:
                    try:
                        return float(lev.get(kk))
                    except Exception:
                        return None

        return None

    @staticmethod
    def _extract_leverage_from_position(position: dict[str, Any] | None) -> float | None:
        """
        Prefer extracting leverage from user_state position structure:
        - leverage: {"type":"cross","value":"20"} or numeric
        - fallback: derive from positionValue / marginUsed
        """
        if not isinstance(position, dict):
            return None

        lev = position.get("leverage")
        if isinstance(lev, dict):
            for kk in ("value", "leverage", "multiplier"):
                if kk in lev:
                    try:
                        return float(lev.get(kk))
                    except Exception:
                        return None
        try:
            if lev is not None:
                return float(lev)
        except Exception:
            pass

        try:
            pv = float(position.get("positionValue") or position.get("positionValueUsd") or position.get("positionValueUsdc"))
            mu = float(position.get("marginUsed") or position.get("marginUsedUsd") or position.get("marginUsedUsdc"))
            if mu > 0:
                return abs(pv) / mu
        except Exception:
            return None

        return None

    def leader_leverage(self, fill: FillEvent, leader_position: dict[str, Any] | None = None) -> float | None:
        lev = self._extract_leverage_from_position(leader_position)
        if lev is not None:
            return lev
        return self._extract_leverage_from_fill(fill)

    @staticmethod
    def position_is_cross(position: dict[str, Any] | None) -> bool | None:
        """
        Determine margin mode from user_state position structure (cross/isolated).
        - leverage.type: "cross" / "isolated"
        - Some payloads may not include it; return None.
        """
        if not isinstance(position, dict):
            return None
        lev = position.get("leverage")
        if isinstance(lev, dict):
            t = lev.get("type")
            if t is None:
                return None
            t = str(t).strip().lower()
            if t == "cross":
                return True
            if t == "isolated":
                return False
        return None

    @staticmethod
    def desired_user_leverage_for_open_add(
        *, leader_leverage: float | None, user_max_leverage: float | None
    ) -> int | None:
        """
        Only used to adjust user's leverage on OPEN/ADD (cross):

        - If user sets max leverage:
          - leader > max -> use max
          - leader <= max -> use leader
        - If user does not set max leverage:
          - use leader

        Return int (SDK update_leverage requires int), ensure >= 1; return None if unknown.
        """
        if leader_leverage is None:
            return None
        try:
            lev = float(leader_leverage)
        except Exception:
            return None
        if lev <= 0:
            return None

        if user_max_leverage is not None:
            try:
                mx = float(user_max_leverage)
            except Exception:
                mx = None
            if mx is not None and mx > 0:
                lev = min(lev, mx)

        lev_i = int(lev)
        if lev_i < 1:
            lev_i = 1
        return lev_i

    @staticmethod
    def liquidation_distance_pct(*, szi: float, mid: float, liquidation_px: float) -> float | None:
        """
        Compute "distance to liquidation" in percent (%):

        - Long (szi > 0): pct = (mid - liq) / mid * 100
        - Short (szi < 0): pct = (liq - mid) / mid * 100

        Returns:
        - None: cannot compute (mid/liquidation_px <= 0 or szi == 0)
        - otherwise >= 0 (guard tiny negative due to rounding)
        """
        try:
            s = float(szi)
            m = float(mid)
            liq = float(liquidation_px)
        except Exception:
            return None

        if abs(s) <= 1e-12:
            return None
        if m <= 0 or liq <= 0:
            return None

        if s > 0:
            pct = (m - liq) / m * 100.0
        else:
            pct = (liq - m) / m * 100.0
        if pct < 0:
            pct = 0.0
        return float(pct)

    def plan_size_with_scale(self, fill: FillEvent, scale: float) -> float:
        """
        Allow passing an "effective scale" from outside (e.g. after margin-based scaling).
        """
        planned_sz = fill.sz * float(scale)
        if self.max_leverage is None:
            return planned_sz

        leader_lev = self._extract_leverage_from_fill(fill)
        if leader_lev is None:
            return planned_sz

        if leader_lev > 0 and leader_lev > self.max_leverage:
            planned_sz *= float(self.max_leverage) / float(leader_lev)
        return planned_sz

    def allow_notional_usd(self, notional_usd: float) -> bool:
        if self.max_notional_usd is not None and notional_usd > self.max_notional_usd:
            return False
        return True

