from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal
import os
from typing import Any, Protocol

from src.hyperliquid.market_data import build_hyperliquid_info_url


class InfoLike(Protocol):
    def user_state(self, address: str) -> dict[str, Any]: ...
    def spot_user_state(self, address: str) -> dict[str, Any]: ...
    def open_orders(self, address: str) -> list[dict[str, Any]]: ...


@dataclass(frozen=True)
class AccountState:
    wallet_address: str
    usdc_total: Decimal
    usdc_hold: Decimal
    unrealized_pnl: Decimal
    open_positions: dict[str, dict[str, Any]]
    account_value: Decimal

    @property
    def free_usdc(self) -> Decimal:
        return self.usdc_total - self.usdc_hold

    @property
    def margin_usage_pct(self) -> Decimal:
        base = self.usdc_total if self.usdc_total > 0 else self.account_value
        return (self.usdc_hold / base * Decimal("100")) if base > 0 else Decimal("0")


def parse_account_state(wallet_address: str, user_state: dict[str, Any], spot_user_state: dict[str, Any]) -> AccountState:
    positions: dict[str, dict[str, Any]] = {}
    unrealized = Decimal("0")
    for row in user_state.get("assetPositions", []):
        pos = row.get("position", {})
        szi = Decimal(str(pos.get("szi", "0")))
        if szi != 0:
            coin = str(pos.get("coin"))
            positions[coin] = pos
            unrealized += Decimal(str(pos.get("unrealizedPnl", "0")))
    usdc_total = Decimal("0")
    usdc_hold = Decimal("0")
    for bal in spot_user_state.get("balances", []):
        if bal.get("coin") == "USDC":
            usdc_total = Decimal(str(bal.get("total", "0")))
            usdc_hold = Decimal(str(bal.get("hold", "0")))
            break
    margin_summary = user_state.get("marginSummary", {})
    account_value = Decimal(str(margin_summary.get("accountValue", usdc_total + unrealized)))
    return AccountState(wallet_address, usdc_total, usdc_hold, unrealized, positions, account_value)


def build_info_client(env: str | None = None) -> InfoLike:
    # Import inside the factory so read-only tests/tools do not import any Exchange/order module.
    from hyperliquid.info import Info  # type: ignore

    return Info(build_hyperliquid_info_url(env).removesuffix("/info"), skip_ws=True)


class HyperliquidAccountStateClient:
    """Read-only account-state adapter around Hyperliquid Info. No Exchange imports."""

    def __init__(self, info_client: InfoLike | None = None, env: str | None = None) -> None:
        self.env = (env or os.getenv("CTB_HL_ENV") or "mainnet").lower()
        self.info_client = info_client or build_info_client(self.env)

    def get_user_state(self, address: str) -> dict[str, Any]:
        return self.info_client.user_state(address)

    def get_spot_user_state(self, address: str) -> dict[str, Any]:
        return self.info_client.spot_user_state(address)

    def get_open_positions(self, address: str) -> dict[str, dict[str, Any]]:
        state = self.get_user_state(address)
        return parse_account_state(address, state, {"balances": []}).open_positions

    def get_open_orders(self, address: str) -> list[dict[str, Any]]:
        return self.info_client.open_orders(address)

    def get_account_equity(self, address: str) -> Decimal:
        state = self.get_user_state(address)
        summary = state.get("marginSummary", {})
        return Decimal(str(summary.get("accountValue", "0")))

    def get_free_usdc(self, address: str) -> Decimal:
        user = self.get_user_state(address)
        spot = self.get_spot_user_state(address)
        return parse_account_state(address, user, spot).free_usdc

    def get_margin_usage(self, address: str) -> Decimal:
        user = self.get_user_state(address)
        spot = self.get_spot_user_state(address)
        return parse_account_state(address, user, spot).margin_usage_pct
