#!/usr/bin/env python3
"""Fetch a Hyperliquid wallet's positions, fills, and PnL via the public info API.

Usage:
  hl_fetch.py positions 0xADDR          # current perp positions
  hl_fetch.py fills 0xADDR [--days 30]  # trade history + per-coin stats
  hl_fetch.py portfolio 0xADDR          # account value / PnL history summary
  hl_fetch.py summary 0xADDR [--days 30]  # all of the above, compact

Outputs JSON to stdout. No API key required.
"""
import argparse
import json
import sys
import time
import urllib.request

API = "https://api.hyperliquid.xyz/info"


def post(payload):
    req = urllib.request.Request(
        API,
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json"},
    )
    for attempt in range(3):
        try:
            with urllib.request.urlopen(req, timeout=30) as r:
                return json.load(r)
        except Exception as e:
            if attempt == 2:
                raise
            time.sleep(2 * (attempt + 1))


def get_positions(addr):
    state = post({"type": "clearinghouseState", "user": addr})
    positions = []
    for ap in state.get("assetPositions", []):
        p = ap.get("position", {})
        szi = float(p.get("szi", 0))
        if szi == 0:
            continue
        positions.append({
            "coin": p.get("coin"),
            "side": "long" if szi > 0 else "short",
            "size": abs(szi),
            "entry_px": float(p.get("entryPx") or 0),
            "position_value_usd": float(p.get("positionValue") or 0),
            "unrealized_pnl": float(p.get("unrealizedPnl") or 0),
            "leverage": p.get("leverage", {}).get("value"),
            "leverage_type": p.get("leverage", {}).get("type"),
        })
    ms = state.get("marginSummary", {})
    return {
        "account_value_usd": float(ms.get("accountValue") or 0),
        "total_position_value_usd": float(ms.get("totalNtlPos") or 0),
        "positions": sorted(positions, key=lambda x: -x["position_value_usd"]),
    }


def get_fills(addr, days):
    start = int((time.time() - days * 86400) * 1000)
    fills = post({"type": "userFillsByTime", "user": addr, "startTime": start})
    by_coin = {}
    for f in fills:
        c = by_coin.setdefault(f["coin"], {
            "trades": 0, "buy_notional": 0.0, "sell_notional": 0.0,
            "realized_pnl": 0.0, "open_count": 0, "close_count": 0,
        })
        notional = float(f["px"]) * float(f["sz"])
        c["trades"] += 1
        if f["side"] == "B":
            c["buy_notional"] += notional
        else:
            c["sell_notional"] += notional
        c["realized_pnl"] += float(f.get("closedPnl") or 0)
        if "Open" in f.get("dir", ""):
            c["open_count"] += 1
        elif "Close" in f.get("dir", ""):
            c["close_count"] += 1
    for c in by_coin.values():
        for k in ("buy_notional", "sell_notional", "realized_pnl"):
            c[k] = round(c[k], 2)
    return {
        "days": days,
        "total_fills": len(fills),
        "note": "API caps at 2000 most recent fills" if len(fills) >= 2000 else "",
        "first_fill_time": fills[0]["time"] if fills else None,
        "last_fill_time": fills[-1]["time"] if fills else None,
        "by_coin": dict(sorted(
            by_coin.items(),
            key=lambda kv: -(kv[1]["buy_notional"] + kv[1]["sell_notional"]))),
    }


def get_portfolio(addr):
    data = post({"type": "portfolio", "user": addr})
    out = {}
    for period, d in data:
        pnl = d.get("pnlHistory", [])
        acct = d.get("accountValueHistory", [])
        out[period] = {
            "pnl": float(pnl[-1][1]) if pnl else None,
            "account_value": float(acct[-1][1]) if acct else None,
            "volume": float(d.get("vlm") or 0),
        }
    return out


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("cmd", choices=["positions", "fills", "portfolio", "summary"])
    ap.add_argument("address")
    ap.add_argument("--days", type=int, default=30)
    args = ap.parse_args()
    addr = args.address.lower()

    if args.cmd == "positions":
        result = get_positions(addr)
    elif args.cmd == "fills":
        result = get_fills(addr, args.days)
    elif args.cmd == "portfolio":
        result = get_portfolio(addr)
    else:
        result = {
            "address": addr,
            "positions": get_positions(addr),
            "fills": get_fills(addr, args.days),
            "portfolio": get_portfolio(addr),
        }
    json.dump(result, sys.stdout, indent=1)
    print()


if __name__ == "__main__":
    main()
