import json
import os
import sys

import eth_account
from eth_account.signers.local import LocalAccount
from hyperliquid.exchange import Exchange
from hyperliquid.info import Info
from hyperliquid.utils import constants as hl_constants

from .constants import REFERRER_CODE
from .secrets import resolve_private_key_from_env


def get_exchange(testnet: bool = False):
    print("Testnet" if testnet else "Mainnet")
    # Private key is required (proxy key or main wallet key)
    try:
        private_key = resolve_private_key_from_env()
    except Exception as e:  # noqa: BLE001
        print(json.dumps({"error": f"Encrypted private key env is not set: {str(e)}"}))
        sys.exit(1)
    if not private_key.startswith("0x"):
        private_key = "0x" + private_key

    api_url = hl_constants.TESTNET_API_URL if testnet else hl_constants.MAINNET_API_URL

    wallet: LocalAccount = eth_account.Account.from_key(private_key)
    signer_addr = wallet.address

    env_address = (os.getenv("HYPERLIQUID_WALLET_ADDRESS") or "").strip()
    if env_address:
        address = env_address
    else:
        address = signer_addr

    if env_address and signer_addr.lower() != env_address.lower():
        print(
            "Note: the signing address (API wallet) is "
            + signer_addr
            + ". HYPERLIQUID_WALLET_ADDRESS is only used as the 'user' field for some endpoints; "
            "it does NOT replace the signer for order/market_open. Please authorize this API wallet "
            "on Hyperliquid, otherwise you may see: 'User or API Wallet ... does not exist'.",
            file=sys.stderr,
        )

    print("signer (API wallet):", signer_addr)
    print("account_address (env or signer):", address)
    return Exchange(wallet=wallet, account_address=address, base_url=api_url, perp_dexs=["", "xyz"]), address


# Compute trading precision
def query_decimals(exchange: Exchange, name: str):
    asset = exchange.info.name_to_asset(name)

    sz_decimal = exchange.info.asset_to_sz_decimals[asset]
    px_decimal = 6 - exchange.info.asset_to_sz_decimals[asset]

    return sz_decimal, px_decimal


def exchange_order(args):
    # Trading requires a private key
    exchange, address = get_exchange(args.testnet)
    # Enable xyz abstraction
    exchange.user_dex_abstraction(address, True)
    exchange.set_referrer(REFERRER_CODE)
    result = []
    cmd = str(getattr(args, "exchange_cmd", "") or "")
    if cmd == "place":
        sz_decimal, px_decimal = query_decimals(exchange, args.coin)

        limit_px = round(args.limit_px, px_decimal)
        result = exchange.order(
            name=args.coin,
            is_buy=args.is_buy,
            sz=round(args.qty, sz_decimal),
            limit_px=limit_px,
            order_type={"limit": {"tif": args.tif}},
            reduce_only=args.reduce_only,
        )
    elif cmd == "market":
        sz_decimal, px_decimal = query_decimals(exchange, args.coin)
        result = exchange.market_open(
            name=args.coin,
            is_buy=args.is_buy,
            sz=round(args.qty, sz_decimal),
            slippage=args.slippage,
        )
    elif cmd == "cancel":
        info = Info(skip_ws=True, perp_dexs=["", "xyz"])
        orders = info.open_orders(address)
        oid = args.oid
        coin = args.coin
        for item in orders:
            tmp_oid = item.get("oid")
            tmp_coin = item.get("coin")
            rst = []
            if oid is None and coin is None:
                rst = exchange.cancel(tmp_coin, tmp_oid)
            elif coin and coin == tmp_coin:
                rst = exchange.cancel(tmp_coin, tmp_oid)
            elif oid and oid == tmp_oid:
                rst = exchange.cancel(tmp_coin, tmp_oid)
            if rst:
                result.append(rst)
    elif cmd == "leverage":
        result = exchange.update_leverage(args.leverage, args.coin, args.is_cross)
    elif cmd == "close":
        sz_decimal, px_decimal = query_decimals(exchange, args.coin)
        result = exchange.market_close(
            coin=args.coin,
            sz=round(args.qty, sz_decimal),
            slippage=args.slippage,
        )
    elif cmd in {"isolated-margein", "isolated-margin"}:
        result = exchange.update_isolated_margin(amount=args.amount, name=args.coin)
    return result

