from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from hyperliquid.info import Info

from .hyperliquid_constants import DEFAULT_HTTP_BASE_URL


class HyperliquidError(RuntimeError):
    pass


@dataclass(frozen=True)
class HyperliquidInfoClient:
    base_url: str = DEFAULT_HTTP_BASE_URL

    def _info(self) -> Info:
        return Info(self.base_url, skip_ws=True)

    @staticmethod
    def _call_with_fallbacks(fn: Any, /, *args: Any, **kwargs: Any) -> Any:
        try:
            return fn(*args, **kwargs)
        except TypeError:
            for k in list(kwargs.keys()):
                k2 = dict(kwargs)
                k2.pop(k, None)
                try:
                    return fn(*args, **k2)
                except TypeError:
                    continue
            raise

    def user_fills(self, user: str) -> list[dict[str, Any]]:
        """
        SDK: `Info.user_fills` currently accepts only `address` (no aggregate_by_time).
        """
        try:
            info = self._info()
            fn = getattr(info, "user_fills")
            # `hyperliquid-python-sdk` current signature: user_fills(address: str) -> Any
            try:
                data = fn(address=user)
            except TypeError:
                # some SDK versions may use user=address
                data = fn(user=user)
        except Exception as e:
            raise HyperliquidError(f"user_fills failed: {e}") from e
        if not isinstance(data, list):
            raise HyperliquidError(f"unexpected user_fills response type: {type(data)}")
        return data  # type: ignore[return-value]

    def open_orders(self, user: str) -> list[dict[str, Any]]:
        try:
            info = self._info()
            fn = getattr(info, "open_orders")
            try:
                data = self._call_with_fallbacks(fn, address=user)
            except TypeError:
                data = self._call_with_fallbacks(fn, user=user)
        except Exception as e:
            raise HyperliquidError(f"open_orders failed: {e}") from e
        if not isinstance(data, list):
            raise HyperliquidError(f"unexpected open_orders response type: {type(data)}")
        return data  # type: ignore[return-value]

    def user_state(self, user: str, dex: str = "") -> dict[str, Any]:
        """
        Current state such as positions/margin (perp/spot depends on response structure)

        Response typically includes:
        - assetPositions: [{ type, position: {...} }, ...]
        - crossMarginSummary / marginSummary
        - withdrawable
        """
        try:
            info = self._info()
            try:
                data = info.user_state(user, dex=dex)
            except TypeError:
                # compatibility with different SDK versions
                data = info.user_state(user)
            if isinstance(data, dict):
                return data
            return dict(data)
        except Exception as e:
            raise HyperliquidError(f"user_state failed: {e}") from e

    def user_state_no_dex(self, user: str) -> dict[str, Any]:
        """
        Call without dex so SDK uses the default dex scope.
        """
        try:
            info = self._info()
            data = info.user_state(user)
            if isinstance(data, dict):
                return data
            return dict(data)
        except Exception as e:
            raise HyperliquidError(f"user_state_no_dex failed: {e}") from e

    def user_fills_by_time(
        self,
        user: str,
        start_time_ms: int,
        end_time_ms: int | None = None,
        *,
        aggregate_by_time: bool = False,
    ) -> list[dict[str, Any]]:
        """
        Fetch fills in a time window (recommended for recent-30d analysis to avoid full history).
        """
        try:
            info = self._info()
            fn = getattr(info, "user_fills_by_time")
            data = fn(
                address=user,
                start_time=int(start_time_ms),
                end_time=(int(end_time_ms) if end_time_ms is not None else None),
                aggregate_by_time=bool(aggregate_by_time),
            )  # type: ignore[misc]
        except Exception as e:
            raise HyperliquidError(f"user_fills_by_time failed: {e}") from e

        if not isinstance(data, list):
            raise HyperliquidError(f"unexpected user_fills_by_time response type: {type(data)}")
        return data  # type: ignore[return-value]

    def user_non_funding_ledger_updates(
        self,
        user: str,
        start_time_ms: int,
        end_time_ms: int | None = None,
    ) -> list[dict[str, Any]]:
        """
        Non-funding ledger updates (deposit/withdraw/transfer/liquidation, etc; excludes funding payments)
        """
        try:
            info = self._info()
            fn = getattr(info, "user_non_funding_ledger_updates")
            data = fn(
                user=user,
                startTime=int(start_time_ms),
                endTime=(int(end_time_ms) if end_time_ms is not None else None),
            )  # type: ignore[misc]
        except Exception as e:
            raise HyperliquidError(f"user_non_funding_ledger_updates failed: {e}") from e

        if not isinstance(data, list):
            raise HyperliquidError(
                f"unexpected user_non_funding_ledger_updates response type: {type(data)}"
            )
        return data  # type: ignore[return-value]

    def portfolio(self, user: str) -> Any:
        """
        Portfolio endpoint: returns pnlHistory/accountValueHistory under day/week/month/allTime, etc.
        """
        try:
            info = self._info()
            fn = getattr(info, "portfolio", None)
            if fn is None:
                # fallback to endpoint type if method name differs
                return info.post("/info", {"type": "portfolio", "user": user})
            data = fn(user)
            return data
        except Exception as e:
            raise HyperliquidError(f"portfolio failed: {e}") from e

