"""
Hyperliquid instrument catalog — loads perpetual contracts as NT CryptoPerpetual.

Fetches exchange metadata (universe + asset contexts) from Hyperliquid info API
and builds NautilusTrader CryptoPerpetual instrument definitions.
"""
from __future__ import annotations

import logging
from datetime import datetime, timezone
from decimal import Decimal

import requests
from nautilus_trader.model.identifiers import InstrumentId, Symbol, Venue
from nautilus_trader.model.instruments import CryptoPerpetual
from nautilus_trader.model.objects import Currency, Price, Quantity

logger = logging.getLogger(__name__)

HL_VENUE = Venue("HYPERLIQUID")
TESTNET_API = "https://api.hyperliquid-testnet.xyz/info"
MAINNET_API = "https://api.hyperliquid.xyz/info"


def _hl_meta(testnet: bool = True) -> dict:
    url = TESTNET_API if testnet else MAINNET_API
    resp = requests.post(url, json={"type": "metaAndAssetCtxs"}, timeout=15)
    resp.raise_for_status()
    data = resp.json()
    if not isinstance(data, list) or len(data) < 2:
        raise ValueError("Invalid metaAndAssetCtxs response")
    return {"universe": data[0].get("universe", []), "contexts": data[1]}


def _to_instrument(asset: dict, ctx: dict | None) -> CryptoPerpetual | None:
    name = asset.get("name", "")
    if not name:
        return None

    symbol_str = f"{name}-USD-PERP"
    inst_id = InstrumentId(Symbol(symbol_str), HL_VENUE)

    px_ctx = ctx if ctx else {}
    mark_px = float(px_ctx.get("markPx", 0) or 0)

    step_size = asset.get("szDecimals", 5)
    size_increment_val = 10 ** -step_size
    tick_size = asset.get("pxDecimals", 1)
    price_increment_val = 10 ** -tick_size

    now_ns = int(datetime.now(timezone.utc).timestamp() * 1e9)

    return CryptoPerpetual(
        instrument_id=inst_id,
        raw_symbol=Symbol(symbol_str),
        base_currency=Currency.from_str(name),
        quote_currency=Currency.from_str("USD"),
        settlement_currency=Currency.from_str("USD"),
        is_inverse=False,
        price_precision=tick_size,
        size_precision=step_size,
        price_increment=Price.from_str(str(price_increment_val)),
        size_increment=Quantity.from_str(str(size_increment_val)),
        multiplier=Quantity.from_str("1.0"),
        maker_fee=Decimal("0.0002"),
        taker_fee=Decimal("0.0005"),
        max_quantity=Quantity.from_str("10000.0"),
        min_quantity=Quantity.from_str(str(size_increment_val)),
        max_notional=None,
        min_notional=None,
        max_price=Price.from_str(str(int(mark_px * 10)) if mark_px > 0 else "10000000.0"),
        min_price=Price.from_str("0.01"),
        margin_init=Decimal("0.02"),
        margin_maint=Decimal("0.01"),
        ts_event=now_ns,
        ts_init=now_ns,
    )


class HyperliquidInstrumentCatalog:
    """Fetches and caches Hyperliquid perpetual instrument definitions."""

    def __init__(self, testnet: bool = True):
        self._testnet = testnet
        self._instruments: dict[str, CryptoPerpetual] = {}

    @property
    def venue(self) -> Venue:
        return HL_VENUE

    def load(self, assets: list[str] | None = None) -> dict[str, CryptoPerpetual]:
        """Fetch all perps, returning dict keyed by base currency name."""
        meta = _hl_meta(testnet=self._testnet)
        universe = meta["universe"]
        contexts = meta["contexts"]

        for i, asset_info in enumerate(universe):
            name = asset_info.get("name", "")
            if not name:
                continue
            if assets and name.upper() not in [a.upper() for a in assets]:
                continue
            ctx = contexts[i] if i < len(contexts) else None
            try:
                inst = _to_instrument(asset_info, ctx)
                if inst:
                    self._instruments[name] = inst
            except Exception as e:
                logger.warning("Skipped instrument %s: %s", name, e)

        logger.info("Loaded %d Hyperliquid instruments", len(self._instruments))
        return self._instruments

    def get(self, name: str) -> CryptoPerpetual | None:
        return self._instruments.get(name.upper())

    def all_ids(self) -> list[InstrumentId]:
        return [inst.id for inst in self._instruments.values()]

    def __len__(self) -> int:
        return len(self._instruments)

    def __iter__(self):
        return iter(self._instruments.values())
