from __future__ import annotations

import argparse
from datetime import datetime, timezone
from decimal import Decimal
import json
import os
from pathlib import Path
import uuid

from eth_account import Account
from hyperliquid.exchange import Exchange
from hyperliquid.utils.types import Cloid

from src.config.hyperliquid_env import HyperliquidEnvConfig, load_hyperliquid_env
from src.execution.order_intent import OrderIntent
from src.execution.payloads import prepare_order_payload
from src.hyperliquid.market_data import HyperliquidMarketData
from src.hyperliquid.rounding import round_hyperliquid_price


def _journal(prefix: str, payload: dict) -> Path:
    path = Path("runtime/reports") / f"{prefix}_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}.json"
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(payload, indent=2, default=str, sort_keys=True), encoding="utf-8")
    return path


def _testnet_exchange() -> tuple[Exchange, HyperliquidEnvConfig]:
    cfg = load_hyperliquid_env("testnet", validation_mode="signed", allow_errors=False)
    if not cfg.safe_to_run_testnet_smokes:
        raise PermissionError("testnet_env_not_safe")
    wallet = Account.from_key(cfg.api_private_key)
    return Exchange(wallet, "https://api.hyperliquid-testnet.xyz", account_address=cfg.account_address), cfg


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description="Hyperliquid testnet ALO+Cancel smoke. Never runs on mainnet.")
    parser.add_argument("--coin", required=True)
    parser.add_argument("--notional", required=True, type=Decimal)
    parser.add_argument("--mode", choices=["alo-cancel"], required=True)
    args = parser.parse_args(argv)
    result: dict = {"env": os.getenv("CTB_HL_ENV"), "coin": args.coin.upper(), "mode": args.mode, "mainnet_blocked": True}
    if os.getenv("CTB_HL_ENV", "").lower() != "testnet":
        result.update(status="skipped", reason="CTB_HL_ENV must be testnet")
        print(json.dumps(result, indent=2, sort_keys=True)); return 0
    if os.getenv("CTB_TESTNET_TRADING_ALLOWED", "").lower() != "true":
        result.update(status="skipped", reason="CTB_TESTNET_TRADING_ALLOWED must be true")
        print(json.dumps(result, indent=2, sort_keys=True)); return 0
    try:
        md = HyperliquidMarketData(env="testnet")
        meta = md.get_symbol_meta(args.coin)
        mid = md.get_all_mids()[args.coin.upper()]
        limit_px = round_hyperliquid_price(mid * Decimal("0.70"))
        cloid = "0x" + uuid.uuid4().hex
        size = args.notional / limit_px
        intent = OrderIntent("testnet_alo_cancel", f"{args.coin.upper()}/USDC:USDC", args.coin.upper(), "buy", False, "limit", "Alo", size, limit_px, None, limit_px * Decimal("0.95"), None, cloid, "testnet_alo_cancel", Decimal("0"), args.notional)
        prepared = prepare_order_payload(intent, meta, min_notional_usd=Decimal("10"), require_testnet=True)
        if not prepared.accepted:
            result.update(status="rejected", reasons=prepared.reasons, prepared_payload=prepared.payload)
        else:
            if prepared.rounded_size is None or prepared.rounded_price is None:
                result.update(status="rejected", reasons=["rounded_size_or_price_missing"], prepared_payload=prepared.payload)
            else:
                exchange, cfg = _testnet_exchange()
                user_address = cfg.account_address
                def masked(addr: str) -> str:
                    return addr[:6] + "..." + addr[-4:] if addr.startswith("0x") and len(addr) > 12 else "<masked>"
                order_resp = exchange.order(args.coin.upper(), True, float(prepared.rounded_size), float(prepared.rounded_price), {"limit": {"tif": "Alo"}}, reduce_only=False, cloid=Cloid(cloid))
                if order_resp.get("status") == "err":
                    result.update(status="error", reason="testnet_order_rejected", cloid=cloid, rounded_size=str(prepared.rounded_size), rounded_price=str(prepared.rounded_price), order_response=order_resp)
                else:
                    open_before = md._post({"type": "openOrders", "user": user_address})
                    oid = None
                    try:
                        statuses = order_resp.get("response", {}).get("data", {}).get("statuses", [])
                        oid = statuses[0].get("resting", {}).get("oid") if statuses else None
                    except Exception:
                        oid = None
                    cancel_resp = exchange.cancel(args.coin.upper(), int(oid)) if oid is not None else exchange.cancel_by_cloid(args.coin.upper(), Cloid(cloid))
                    open_after = md._post({"type": "openOrders", "user": user_address})
                    result.update(status="ok", account_masked=masked(user_address), cloid=cloid, oid=oid, rounded_size=str(prepared.rounded_size), rounded_price=str(prepared.rounded_price), order_response=order_resp, cancel_response=cancel_resp, open_orders_before_cancel=open_before, open_orders_after_cancel=open_after, no_fill_expected=True)
    except Exception as exc:
        result.update(status="error", reason=type(exc).__name__, message=str(exc))
    path = _journal("hyperliquid_testnet_alo_cancel", result)
    result["journal"] = str(path)
    print(json.dumps(result, indent=2, default=str, sort_keys=True))
    return 0 if result.get("status") in {"ok", "skipped"} else 1


if __name__ == "__main__":
    raise SystemExit(main())
