import json
import os
import secrets
import subprocess
from typing import Any, Optional

import requests
from eth_account import Account
from web3 import Web3
from web3.contract import Contract
from eth_abi import decode as abi_decode
from eth_abi import encode as abi_encode
from . import secrets as wallet_secrets
from .exchange import get_exchange
from .constants import (
    RPC_URL,
    RELAY_API_URL,
    REGISTER_1M_TRADE_API,
    ARB_CHAIN_ID,
    HL_CHAIN_ID,
    HL_USDC_ADDRESS,
    NATIVE_TOKEN,
    ERC20_ABI,
    REFERRER_CODE,
    PRIVATE_KEY_ENC_KEY,
    PRIVATE_KEY_ENC_PASSWORD_KEY,
)

def register_wallet(address: Optional[str] = None, notice: bool = True) -> bool:
    """
    Register deposit address with api.1m-trade.com.

    If ``address`` is omitted or empty, reads ``HYPERLIQUID_WALLET_ADDRESS`` from
    the state ``.env`` file (same path as ``init_env_file``).
    """

    addr = (address or "").strip()
    if not addr:
        env = wallet_secrets.init_env_file()
        addr = (env.get("HYPERLIQUID_WALLET_ADDRESS") or "").strip()
    if not addr:
        print(
            "❌ No deposit address: pass an address argument or set "
            "HYPERLIQUID_WALLET_ADDRESS in "
            f"{wallet_secrets.get_env_path()}"
        )
        return False

    quote_payload = {"address": addr}
    register_res = requests.post(
        REGISTER_1M_TRADE_API,
        headers={"Content-Type": "application/json"},
        json=quote_payload,
        timeout=30,
    )
    data = register_res.json()
    if data.get("code") != 0:
        # JS uses: data.message
        print(f"❌ Failed to register 1M trade wallet: {data.get('message')}")
        return False

    if notice:
        print("✅ 1M trade wallet registered successfully")
    return True


def create_wallet(openclaw_target: Optional[str] = None, lang: str = "en") -> Optional[str]:
    """
    Create a new deposit wallet:
    - encrypt and persist private key in local state ".env"
    - optionally send private key to OpenClaw target (Stage 3)
    """

    env = wallet_secrets.init_env_file()
    existing_key_enc = (env.get(PRIVATE_KEY_ENC_KEY) or "").strip()
    existing_address = (env.get("HYPERLIQUID_WALLET_ADDRESS") or "").strip()
    if existing_key_enc or existing_address:
        print(
            "⚠️ Existing wallet detected. Please back up and remove "
            f"{PRIVATE_KEY_ENC_KEY} and "
            f"HYPERLIQUID_WALLET_ADDRESS from {wallet_secrets.get_env_path()} before creating a new one."
        )
        return None

    wallet = Account.create()
    # create-wallet will only persist the generated wallet (encrypted private key).

    print("### 🆕 Relay funding wallet generated\n")
    print(f"> **Deposit address**: `{wallet.address}`")
    print(
        "> **Your private key is stored encrypted in the file. (real wallet; never print or expose it; view locally only)**"
    )
    print(
        "> **If you want to view your private key, you can say: 'What is my private key?' and I can send it to you in a more secure way.**"
    )
    print("\n⚠️ Quick start:")
    print("1. Deposit **USDC on Arbitrum** to this address.")
    print("2. The system will use Relay to bridge to Hyperliquid automatically.")
    print("3. After deposit, run the listener to continue.")

    env["HYPERLIQUID_WALLET_ADDRESS"] = wallet.address

    enc_password = (
        os.environ.get(PRIVATE_KEY_ENC_PASSWORD_KEY) or env.get(PRIVATE_KEY_ENC_PASSWORD_KEY) or ""
    ).strip()
    if not enc_password:
        enc_password = secrets.token_bytes(32).hex()
    env[PRIVATE_KEY_ENC_PASSWORD_KEY] = enc_password

    # JS uses: wallet.privateKey (starts with 0x)
    plain_private_key_hex = wallet.key.hex()
    if not plain_private_key_hex.startswith("0x"):
        plain_private_key_hex = "0x" + plain_private_key_hex

    env[PRIVATE_KEY_ENC_KEY] = wallet_secrets.encrypt_private_key(plain_private_key_hex, enc_password)
    wallet_secrets.set_env_file(env)

    # Register deposit address with api.1m-trade.com after wallet is persisted.
    register_wallet(wallet.address, notice=True)

    # Optional Stage 3: first non-flag arg after createWallet = OpenClaw user ID
    if openclaw_target:
        print("\n📤 Sending private key via OpenClaw secure channel (Stage 3)...")
        out = send_private_key_to_target(openclaw_target, lang=lang)
        if not out["ok"]:
            print(
                "⚠️ Stage 3 (sendPrivateKey) failed: "
                f"{out.get('error') or out.get('status')}. "
                "Please send manually through OpenClaw if needed."
            )
        else:
            print("✅ Private key sent via OpenClaw. In chat, only confirm: sent via secure channel (do not paste the key).")

    return wallet.address


def init_wallet(address: Optional[str], pri_key: str) -> Optional[str]:
    """
    Initialize wallet from user-provided private key (+ optional address).

    Safety rules:
    - If the env file already contains a wallet address and/or encrypted key and/or encryption password,
      we refuse to overwrite.
    - We encrypt the private key using the same AES-GCM+scrypt scheme as create_wallet.
    """

    env = wallet_secrets.init_env_file()

    existing_address = (env.get("HYPERLIQUID_WALLET_ADDRESS") or "").strip()
    existing_key_enc = (env.get(PRIVATE_KEY_ENC_KEY) or "").strip()
    existing_enc_password = (env.get(PRIVATE_KEY_ENC_PASSWORD_KEY) or "").strip()

    if existing_address or existing_key_enc or existing_enc_password:
        print(
            "⚠️ Existing wallet detected. Please back up and remove "
            f"HYPERLIQUID_WALLET_ADDRESS / {PRIVATE_KEY_ENC_KEY} / {PRIVATE_KEY_ENC_PASSWORD_KEY} from "
            f"{wallet_secrets.get_env_path()} before init-wallet."
        )
        return None

    addr = str(address or "").strip()
    pk = str(pri_key or "").strip()
    if not pk:
        raise ValueError("--pri_key is required")

    if not pk.startswith("0x"):
        pk = "0x" + pk

    account = Account.from_key(pk)
    derived_address = account.address

    # If address is omitted, derive from private key.
    if not addr:
        # If address is omitted, derive from private key.
        addr = derived_address
    else:
        # If user supplies address, we accept it as-is (no derived-address validation),
        # because user may use a proxy private key.
        if not addr.startswith("0x"):
            addr = "0x" + addr

    enc_password = secrets.token_bytes(32).hex()
    env["HYPERLIQUID_WALLET_ADDRESS"] = addr
    env[PRIVATE_KEY_ENC_PASSWORD_KEY] = enc_password
    env[PRIVATE_KEY_ENC_KEY] = wallet_secrets.encrypt_private_key(pk, enc_password)
    wallet_secrets.set_env_file(env)

    return addr


def _private_key_openclaw_message(lang: str, pk: str, env_path: str) -> str:
    """Bilingual text for printing and OpenClaw `message send` (lang: zh | en)."""

    code = (lang or "en").strip().lower()
    if code == "zh":
        return (
            "⚠️ 这是您的钱包私钥（请立即妥善保存，然后删除本消息）：\n\n"
            f"私钥：{pk}\n\n"
            "安全提示：\n"
            "1. 任何获得此私钥的人均可完全支配资金。\n"
            "2. 请复制到安全位置（纸质备份 / 加密存储）。请勿截图。\n"
            "3. 保存后请删除聊天记录。\n"
            "4. 切勿向任何人泄露。\n\n"
            f"您也可以在本地文件中查看私钥：{env_path}\n\n"
            "如有疑问请回复，我会协助您。"
        )
    return (
        "⚠️ This is your wallet private key (save immediately, then delete this message):\n\n"
        f"Private key: {pk}\n\n"
        "Security reminders:\n"
        "1. Anyone with this key can fully control the funds.\n"
        "2. Copy it to a secure place (paper / encrypted drive). Do NOT take screenshots.\n"
        "3. Delete chat history after saving.\n"
        "4. Never share it with anyone.\n\n"
        f"You can also view the private key in the local file: {env_path}\n\n"
        "If you have questions, reply and I will help."
    )


def send_private_key_to_target(target: str, lang: str = "en") -> dict[str, Any]:
    t = str(target or "").strip()
    if not t:
        return {"ok": False, "error": "missing target"}

    try:
        env = wallet_secrets.init_env_file()
        pk = wallet_secrets.resolve_plain_private_key_from_env_file(env)
    except Exception as e:  # noqa: BLE001
        return {"ok": False, "error": str(e)}

    if not pk:
        return {"ok": False, "error": "Private key not found"}

    env_path = str(wallet_secrets.get_env_path())
    message = _private_key_openclaw_message(lang, pk, env_path)
    args = ["openclaw", "message", "send", "--target", t, "--message", message]
    try:
        result = subprocess.run(args, check=False)
        status = int(result.returncode or 0)
    except FileNotFoundError as e:
        return {"ok": False, "error": str(e)}

    return {"ok": status == 0, "status": status}


def _build_web3() -> Web3:
    return Web3(Web3.HTTPProvider(RPC_URL))


def _erc20_contract(w3: Web3) -> Contract:
    return w3.eth.contract(address=Web3.to_checksum_address(NATIVE_TOKEN), abi=ERC20_ABI)


def _send_tx_and_wait(
    w3: Web3, account: Account, to: str, data: str, value_wei: int
) -> str:
    """
    Port of wallet.sendTransaction({to,data,value,chainId}) + await wait().
    """

    from_addr = account.address
    tx: dict[str, Any] = {
        "to": Web3.to_checksum_address(to),
        "data": data,
        "value": value_wei,
        "chainId": ARB_CHAIN_ID,
        "nonce": w3.eth.get_transaction_count(from_addr),
        # Fee params set below (EIP-1559 preferred)
        "gas": 0,  # set below
    }

    # Estimate gas to match the relay tx requirements.
    gas = w3.eth.estimate_gas(
        {"from": from_addr, "to": tx["to"], "data": data, "value": value_wei}
    )
    tx["gas"] = int(gas)

    # EIP-1559: ensure maxFeePerGas >= baseFeePerGas + priority.
    # This avoids "max fee per gas less than block base fee" rejections.
    latest = w3.eth.get_block("latest")
    base_fee = latest.get("baseFeePerGas")
    if base_fee is not None:
        try:
            priority_fee = int(w3.eth.max_priority_fee)
        except Exception:  # noqa: BLE001
            # Safe fallback for most networks.
            priority_fee = int(w3.to_wei(2, "gwei"))

        base_fee_int = int(base_fee)
        max_priority_fee = int(priority_fee)
        # Use a margin to handle small baseFee oscillations between calls.
        max_fee_per_gas = base_fee_int + max_priority_fee * 2
        if max_fee_per_gas <= base_fee_int:
            max_fee_per_gas = base_fee_int + 1_000_000

        tx["maxFeePerGas"] = max_fee_per_gas
        tx["maxPriorityFeePerGas"] = max_priority_fee
    else:
        # Legacy fallback.
        tx["gasPrice"] = w3.eth.gas_price

    signed = account.sign_transaction(tx)
    raw_tx = getattr(signed, "raw_transaction", None) or getattr(signed, "rawTransaction", None)
    if raw_tx is None:
        raise AttributeError("SignedTransaction has neither raw_transaction nor rawTransaction")
    tx_hash = w3.eth.send_raw_transaction(raw_tx)
    w3.eth.wait_for_transaction_receipt(tx_hash)
    return tx_hash.hex()


def start_listener() -> bool:
    """
    Port of startListener():
    - decrypt private key from state file
    - check Arbitrum USDC balance
    - request Relay quote and execute steps
    - sign Phantom Agent EIP-712 and activate Hyperliquid L1 referrer
    """

    env = wallet_secrets.init_env_file()
    user_pk = wallet_secrets.resolve_plain_private_key_from_env_file(env)
    if not user_pk.startswith("0x"):
        user_pk = "0x" + user_pk

    w3 = _build_web3()
    account = Account.from_key(user_pk)
    target_address = account.address

    print("🔍 Checking Arbitrum USDC balance...")
    usdc = _erc20_contract(w3)
    try:
        usdc_decimals = int(usdc.functions.decimals().call())
    except Exception:  # noqa: BLE001
        usdc_decimals = 6

    usdc_balance = int(usdc.functions.balanceOf(account.address).call())
    if usdc_balance <= 0:
        print("⏳ No USDC balance found on Arbitrum. Please deposit USDC first.")
        return False

    min_usdc = 6 * (10 ** int(usdc_decimals))
    if usdc_balance < min_usdc:
        cur = usdc_balance / (10 ** int(usdc_decimals))
        print(f"⏳ USDC balance is below minimum (6 USDC). Current: {cur} USDC")
        return False

    print(
        "✅ Balance ok. Preparing to bridge ALL USDC "
        f"({usdc_balance / (10 ** int(usdc_decimals))} via Relay V2 to {target_address})."
    )

    def fetch_quote(amount_wei: int) -> dict[str, Any]:
        quote_payload = {
            "user": account.address,
            "originChainId": ARB_CHAIN_ID,
            "destinationChainId": HL_CHAIN_ID,
            "originCurrency": NATIVE_TOKEN,
            "destinationCurrency": HL_USDC_ADDRESS,
            "recipient": target_address,
            "amount": str(amount_wei),
            "tradeType": "EXACT_INPUT",
            "useExternalLiquidity": True,
        }

        relay_res = requests.post(
            RELAY_API_URL,
            headers={"Content-Type": "application/json"},
            json=quote_payload,
            timeout=60,
        )
        quote_data = relay_res.json()
        if not quote_data.get("steps"):
            print("❌ Relay API rejected the request. Raw response:")
            print(json.dumps(quote_data, indent=2, default=str))
            raise RuntimeError(quote_data.get("message") or "Failed to obtain a valid route from Relay API.")
        return quote_data

    def execute_quote_steps(quote_data: dict[str, Any]) -> int:
        sent_count = 0
        steps = quote_data.get("steps") or []
        erc20_approve_selector = "0x095ea7b3"  # approve(address,uint256)
        max_uint256 = 2**256 - 1

        for si, step in enumerate(steps):
            items = step.get("items") or []
            for ii, item in enumerate(items):
                tx_data = (item or {}).get("data")
                if not tx_data:
                    continue
                to = tx_data.get("to")
                calldata = tx_data.get("data")
                if not to or not calldata:
                    continue

                value_raw = tx_data.get("value", 0) or 0
                value = int(value_raw) if not isinstance(value_raw, str) else int(value_raw)

                data = calldata
                if (
                    isinstance(data, str)
                    and data.startswith(erc20_approve_selector)
                    and str(to).lower() == NATIVE_TOKEN.lower()
                ):
                    # Replace approve(value) with approve(MaxUint256) for reliability.
                    try:
                        # calldata after 4-byte selector
                        decoded = abi_decode(["address", "uint256"], bytes.fromhex(data[10:]))
                        spender = decoded[0]
                        encoded_args = abi_encode(["address", "uint256"], [spender, max_uint256]).hex()
                        # Must keep the approve(address,uint256) selector prefix.
                        data = erc20_approve_selector + encoded_args
                    except Exception:
                        pass

                tx_hash = _send_tx_and_wait(
                    w3=w3, account=account, to=to, data=data, value_wei=value
                )
                sent_count += 1
                print(
                    f"⏳ Sent tx [step {si + 1}/{len(steps)} item {ii + 1}/{len(items)}]: {tx_hash}"
                )
        return sent_count

    try:
        before = int(usdc.functions.balanceOf(account.address).call())
        print("\n🌉 [1/3] Requesting Relay quote...")
        quote_data = fetch_quote(before)
        print("🔄 [2/3] Executing Relay steps...")
        sent1 = execute_quote_steps(quote_data)

        after1 = int(usdc.functions.balanceOf(account.address).call())
        if after1 >= before:
            print(
                "ℹ️ USDC balance unchanged after first execution. Re-quoting to continue "
                "(likely approval-only first pass)."
            )
            quote_data = fetch_quote(before)
            sent2 = execute_quote_steps(quote_data)
            after2 = int(usdc.functions.balanceOf(account.address).call())
            if after2 >= before:
                print(
                    f"⚠️ USDC balance still unchanged after re-quote. "
                    f"Sent tx count: {sent1 + sent2}. Please retry startListener once more."
                )
                return False

        print("✅ Bridge submission complete. Funds are expected to arrive shortly.")
        print("\n🔗 [3/3] Registering/activating Hyperliquid L1 trading account...")
        
        referrer_code = REFERRER_CODE
        exchange, address = get_exchange(False)
        hl_result = exchange.set_referrer(referrer_code)
        if hl_result.get("status") == "ok":
            print("✅ L1 account activated successfully. You can start trading now.")
        else:
            print("✅ L1 account is already active. Channel remains available.")

        print("\n🎉 Funding pipeline completed.")
        return True
    except Exception as e:  # noqa: BLE001
        print(f"\n❌ On-chain execution aborted: {e}")
        return False


__all__ = [
    "create_wallet",
    "init_wallet",
    "send_private_key_to_target",
    "start_listener",
    "register_wallet",
]

