"""
Turbo Relayer — submits transactions to Turbo.sol on Monad.

Handles:
  - createMarket() every 5 minutes
  - executeTrade() when orders match
  - resolve() after market expiry with Pyth VAA data
  - getPosition() to query on-chain balances

The relayer wallet pays all gas. Users never pay gas.
"""

import json
import time

import httpx

from config import (
    TURBO_CONTRACT_ADDRESS, MONAD_RPC_URL, MONAD_CHAIN_ID,
    TURBO_RELAYER_KEY, PYTH_BTC_FEED,
)

_w3 = None
_account = None
_contract = None

PYTH_HERMES_VAA_URL = "https://hermes.pyth.network/v2/updates/price/latest"

# Turbo.sol ABI (functions we call)
TURBO_ABI = json.loads("""[
  {
    "inputs": [
      {"name": "marketId", "type": "bytes32"},
      {"name": "strikePrice", "type": "uint256"},
      {"name": "startTime", "type": "uint256"},
      {"name": "endTime", "type": "uint256"}
    ],
    "name": "createMarket",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {"name": "marketId", "type": "bytes32"},
          {"name": "maker", "type": "address"},
          {"name": "side", "type": "uint8"},
          {"name": "outcome", "type": "uint8"},
          {"name": "price", "type": "uint256"},
          {"name": "size", "type": "uint256"},
          {"name": "nonce", "type": "uint256"},
          {"name": "expiration", "type": "uint256"}
        ],
        "name": "buyOrder",
        "type": "tuple"
      },
      {"name": "buySig", "type": "bytes"},
      {
        "components": [
          {"name": "marketId", "type": "bytes32"},
          {"name": "maker", "type": "address"},
          {"name": "side", "type": "uint8"},
          {"name": "outcome", "type": "uint8"},
          {"name": "price", "type": "uint256"},
          {"name": "size", "type": "uint256"},
          {"name": "nonce", "type": "uint256"},
          {"name": "expiration", "type": "uint256"}
        ],
        "name": "sellOrder",
        "type": "tuple"
      },
      {"name": "sellSig", "type": "bytes"},
      {"name": "fillSize", "type": "uint256"}
    ],
    "name": "executeTrade",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {"name": "marketId", "type": "bytes32"},
      {"name": "priceUpdate", "type": "bytes[]"}
    ],
    "name": "resolve",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {"name": "user", "type": "address"},
      {"name": "value", "type": "uint256"},
      {"name": "deadline", "type": "uint256"},
      {"name": "v", "type": "uint8"},
      {"name": "r", "type": "bytes32"},
      {"name": "s", "type": "bytes32"}
    ],
    "name": "approveUsdcWithPermit",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [{"name": "marketId", "type": "bytes32"}],
    "name": "redeem",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [{"name": "marketId", "type": "bytes32"}],
    "name": "isMarketActive",
    "outputs": [{"name": "", "type": "bool"}],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {"name": "marketId", "type": "bytes32"},
      {"name": "user", "type": "address"}
    ],
    "name": "getPosition",
    "outputs": [
      {"name": "yes", "type": "uint256"},
      {"name": "no", "type": "uint256"}
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {"name": "marketId", "type": "bytes32"}
    ],
    "name": "getMarket",
    "outputs": [
      {"name": "strikePrice", "type": "uint256"},
      {"name": "startTime", "type": "uint256"},
      {"name": "endTime", "type": "uint256"},
      {"name": "resolved", "type": "bool"},
      {"name": "outcome", "type": "uint8"}
    ],
    "stateMutability": "view",
    "type": "function"
  }
]""")


def is_live() -> bool:
    return bool(TURBO_CONTRACT_ADDRESS and TURBO_RELAYER_KEY)


def _init():
    global _w3, _account, _contract
    if _w3 is not None:
        return

    if not is_live():
        print("[RELAYER] No contract address or relayer key — running in mock mode", flush=True)
        return

    try:
        from web3 import Web3
        _w3 = Web3(Web3.HTTPProvider(MONAD_RPC_URL))
        _account = _w3.eth.account.from_key(TURBO_RELAYER_KEY)
        _contract = _w3.eth.contract(
            address=Web3.to_checksum_address(TURBO_CONTRACT_ADDRESS),
            abi=TURBO_ABI,
        )
        balance = _w3.eth.get_balance(_account.address)
        print(f"[RELAYER] Connected to Monad — relayer: {_account.address}", flush=True)
        print(f"[RELAYER] Balance: {_w3.from_wei(balance, 'ether'):.4f} MON", flush=True)
    except Exception as e:
        print(f"[RELAYER] Failed to init web3: {e}", flush=True)
        _w3 = None


def _send_tx(tx_func, value=0):
    _init()
    if _w3 is None or _account is None:
        return None

    try:
        tx = tx_func.build_transaction({
            "from": _account.address,
            "nonce": _w3.eth.get_transaction_count(_account.address),
            "gas": 1_000_000,
            "gasPrice": _w3.eth.gas_price,
            "value": value,
            "chainId": MONAD_CHAIN_ID,
        })
        signed = _account.sign_transaction(tx)
        tx_hash = _w3.eth.send_raw_transaction(signed.raw_transaction)
        receipt = _w3.eth.wait_for_transaction_receipt(tx_hash, timeout=30)
        status = "OK" if receipt.status == 1 else "FAILED"
        print(f"[RELAYER] TX {status}: {tx_hash.hex()}", flush=True)
        return receipt
    except Exception as e:
        print(f"[RELAYER] TX error: {e}", flush=True)
        return None


# ── Market Creation ──────────────────────────────────────────

def create_market_onchain(market_id_hex: str, strike_price_6d: int,
                          start_time: int, end_time: int):
    _init()
    if _contract is None:
        print(f"[RELAYER] Mock createMarket: {market_id_hex}", flush=True)
        return

    market_id_bytes = bytes.fromhex(market_id_hex.replace("0x", "").ljust(64, "0"))
    tx_func = _contract.functions.createMarket(
        market_id_bytes, strike_price_6d, start_time, end_time,
    )
    _send_tx(tx_func)


# ── Trade Execution ──────────────────────────────────────────

def execute_trade_onchain(buy_order: dict, buy_sig: str,
                          sell_order: dict, sell_sig: str, fill_size: int) -> bool:
    """Execute trade on-chain. Returns True if TX succeeded."""
    _init()
    if _contract is None:
        print(f"[RELAYER] Mock executeTrade: size={fill_size}", flush=True)
        return True  # mock mode = success

    def to_tuple(o):
        mid = o["marketId"] if o["marketId"].startswith("0x") else "0x" + o["marketId"]
        mid = mid.ljust(66, "0")
        return (
            bytes.fromhex(mid[2:]),
            _w3.to_checksum_address(o["maker"]),
            o["side"], o["outcome"], o["price"], o["size"], o["nonce"], o["expiration"],
        )

    buy_sig_bytes = bytes.fromhex(buy_sig.replace("0x", "")) if buy_sig else b"\x00" * 65
    sell_sig_bytes = bytes.fromhex(sell_sig.replace("0x", "")) if sell_sig else b"\x00" * 65

    # Pre-verify signatures locally before sending to chain
    def verify_sig(order, sig_bytes, label):
        try:
            mid = order["marketId"] if order["marketId"].startswith("0x") else "0x" + order["marketId"]
            mid = mid.ljust(66, "0")
            mid_bytes = bytes.fromhex(mid[2:])

            from eth_abi import encode as abi_encode

            ds_typehash = Web3.keccak(text="EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
            name_hash = Web3.keccak(text="Turbo")
            version_hash = Web3.keccak(text="1")
            domain_sep = Web3.keccak(abi_encode(
                ["bytes32","bytes32","bytes32","uint256","address"],
                [ds_typehash, name_hash, version_hash, MONAD_CHAIN_ID, TURBO_CONTRACT_ADDRESS],
            ))

            ot = Web3.keccak(text="Order(bytes32 marketId,address maker,uint8 side,uint8 outcome,uint256 price,uint256 size,uint256 nonce,uint256 expiration)")
            struct_hash = Web3.keccak(abi_encode(
                ["bytes32","bytes32","address","uint8","uint8","uint256","uint256","uint256","uint256"],
                [ot, mid_bytes, order["maker"], order["side"], order["outcome"], order["price"], order["size"], order["nonce"], order["expiration"]],
            ))
            digest = Web3.keccak(b"\x19\x01" + domain_sep + struct_hash)

            r = int.from_bytes(sig_bytes[:32], "big")
            s = int.from_bytes(sig_bytes[32:64], "big")
            v = sig_bytes[64]
            recovered = _w3.eth.account._recover_hash(digest, vrs=(v, r, s))
            match = recovered.lower() == order["maker"].lower()
            print(f"  {label}: recovered={recovered[:10]} expected={order['maker'][:10]} {'OK' if match else 'MISMATCH!'}", flush=True)
            return match
        except Exception as e:
            print(f"  {label}: verify error: {e}", flush=True)
            return False

    from web3 import Web3

    print(f"[RELAYER] executeTrade debug:", flush=True)
    print(f"  buy:  maker={buy_order['maker'][:10]} side={buy_order['side']} price={buy_order['price']} size={buy_order['size']} nonce={buy_order['nonce']}", flush=True)
    print(f"  sell: maker={sell_order['maker'][:10]} side={sell_order['side']} price={sell_order['price']} size={sell_order['size']} nonce={sell_order['nonce']}", flush=True)
    print(f"  fill_size={fill_size}", flush=True)

    buy_ok = verify_sig(buy_order, buy_sig_bytes, "buy_sig")
    sell_ok = verify_sig(sell_order, sell_sig_bytes, "sell_sig")

    if not buy_ok or not sell_ok:
        print(f"[RELAYER] Signature verification failed locally, skipping on-chain TX", flush=True)
        return False

    if len(buy_sig_bytes) != 65 or len(sell_sig_bytes) != 65:
        print(f"[RELAYER] Bad signature length, skipping", flush=True)
        return False

    tx_func = _contract.functions.executeTrade(
        to_tuple(buy_order),
        buy_sig_bytes,
        to_tuple(sell_order),
        sell_sig_bytes,
        fill_size,
    )
    receipt = _send_tx(tx_func)
    return receipt is not None and receipt.status == 1


# ── Market Resolution (Pyth VAA) ─────────────────────────────

def fetch_pyth_vaa() -> list[bytes] | None:
    """
    Fetch the latest Pyth price update VAA (Verified Action Approval)
    for BTC/USD. This is the signed binary data that Turbo.resolve()
    passes to pyth.updatePriceFeeds() for on-chain verification.
    """
    try:
        feed_id = PYTH_BTC_FEED.replace("0x", "")
        resp = httpx.get(
            PYTH_HERMES_VAA_URL,
            params={"ids[]": feed_id, "encoding": "hex"},
            timeout=10,
        )
        resp.raise_for_status()
        data = resp.json()

        # binary.data contains the hex-encoded VAA bytes
        binary_data = data.get("binary", {}).get("data", [])
        if not binary_data:
            print("[RELAYER] No Pyth VAA data returned", flush=True)
            return None

        vaa_bytes = [bytes.fromhex(vaa) for vaa in binary_data]
        print(f"[RELAYER] Fetched Pyth VAA: {len(vaa_bytes)} update(s), {len(vaa_bytes[0])} bytes", flush=True)
        return vaa_bytes

    except Exception as e:
        print(f"[RELAYER] Pyth VAA fetch error: {e}", flush=True)
        return None


def resolve_market_onchain(market_id_hex: str) -> bool:
    """
    Resolve a market on-chain:
    1. Fetch Pyth VAA (signed price update)
    2. Call Turbo.resolve(marketId, priceUpdate) — pays Pyth fee in MON
    3. Contract verifies Pyth signature, reads BTC price, sets outcome
    """
    _init()
    if _contract is None:
        print(f"[RELAYER] Mock resolve: {market_id_hex}", flush=True)
        return False

    # Fetch signed price data from Pyth
    vaa_bytes = fetch_pyth_vaa()
    if not vaa_bytes:
        print(f"[RELAYER] Cannot resolve {market_id_hex} — no Pyth data", flush=True)
        return False

    market_id_bytes = bytes.fromhex(market_id_hex.replace("0x", "").ljust(64, "0"))

    # Get Pyth update fee
    try:
        from web3 import Web3
        pyth_address = "0x2880aB155794e7179c9eE2e38200202908C17B43"
        pyth_abi = json.loads('[{"inputs":[{"name":"updateData","type":"bytes[]"}],"name":"getUpdateFee","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]')
        pyth_contract = _w3.eth.contract(
            address=Web3.to_checksum_address(pyth_address), abi=pyth_abi,
        )
        update_fee = pyth_contract.functions.getUpdateFee(vaa_bytes).call()
        print(f"[RELAYER] Pyth update fee: {update_fee} wei", flush=True)
    except Exception as e:
        print(f"[RELAYER] Fee query error: {e}, using 1 wei", flush=True)
        update_fee = 1

    tx_func = _contract.functions.resolve(market_id_bytes, vaa_bytes)
    receipt = _send_tx(tx_func, value=update_fee)
    return receipt is not None and receipt.status == 1


# ── On-Chain Queries ─────────────────────────────────────────

def get_onchain_position(market_id_hex: str, user_address: str) -> tuple[int, int] | None:
    """Query Turbo.getPosition(marketId, user) on-chain."""
    _init()
    if _contract is None:
        return None

    try:
        from web3 import Web3
        market_id_bytes = bytes.fromhex(market_id_hex.replace("0x", "").ljust(64, "0"))
        result = _contract.functions.getPosition(
            market_id_bytes,
            Web3.to_checksum_address(user_address),
        ).call()
        return (result[0], result[1])  # (yesBalance, noBalance)
    except Exception as e:
        print(f"[RELAYER] getPosition error: {e}", flush=True)
        return None


def get_onchain_market(market_id_hex: str) -> dict | None:
    """Query Turbo.getMarket(marketId) on-chain."""
    _init()
    if _contract is None:
        return None

    try:
        market_id_bytes = bytes.fromhex(market_id_hex.replace("0x", "").ljust(64, "0"))
        result = _contract.functions.getMarket(market_id_bytes).call()
        return {
            "strikePrice": result[0],
            "startTime": result[1],
            "endTime": result[2],
            "resolved": result[3],
            "outcome": result[4],
        }
    except Exception as e:
        print(f"[RELAYER] getMarket error: {e}", flush=True)
        return None


_trade_cache: list[dict] = []
_last_scanned_block: int = 0


def get_onchain_trades(market_ids: list[str] | None = None) -> list[dict]:
    """
    Read Trade events from Turbo.sol filtered by market IDs.
    Uses indexed marketId topic for efficient on-chain queries.
    Caches results — only fetches new blocks since last call.
    """
    global _trade_cache, _last_scanned_block
    _init()
    if _contract is None:
        return _trade_cache

    try:
        from web3 import Web3

        DEPLOY_BLOCK = 62569552
        latest = _w3.eth.block_number

        if _last_scanned_block == 0:
            _last_scanned_block = DEPLOY_BLOCK

        # Nothing new to scan
        if _last_scanned_block >= latest:
            return _trade_cache

        trade_event_abi = json.loads("""[{
            "anonymous": false,
            "inputs": [
                {"indexed": true, "name": "marketId", "type": "bytes32"},
                {"indexed": true, "name": "buyer", "type": "address"},
                {"indexed": true, "name": "seller", "type": "address"},
                {"indexed": false, "name": "outcome", "type": "uint8"},
                {"indexed": false, "name": "price", "type": "uint256"},
                {"indexed": false, "name": "size", "type": "uint256"},
                {"indexed": false, "name": "fee", "type": "uint256"}
            ],
            "name": "Trade",
            "type": "event"
        }]""")

        contract_with_events = _w3.eth.contract(
            address=Web3.to_checksum_address(TURBO_CONTRACT_ADDRESS),
            abi=trade_event_abi,
        )

        # Build topic filter for specific market IDs if provided
        topic_filter = {}
        if market_ids:
            padded = [bytes.fromhex(mid.replace("0x", "").ljust(64, "0")) for mid in market_ids]
            topic_filter["argument_filters"] = {"marketId": padded}

        # Scan in small chunks (2000 blocks ~ 1 hour on Monad)
        from_block = _last_scanned_block + 1
        chunk_size = 2_000
        new_trades = []

        for start in range(from_block, latest + 1, chunk_size):
            end = min(start + chunk_size - 1, latest)
            try:
                logs = contract_with_events.events.Trade.get_logs(
                    from_block=start, to_block=end,
                )
                for log in logs:
                    block = _w3.eth.get_block(log["blockNumber"])
                    new_trades.append({
                        "marketId": "0x" + log["args"]["marketId"].hex(),
                        "buyer": log["args"]["buyer"],
                        "seller": log["args"]["seller"],
                        "outcome": log["args"]["outcome"],
                        "price": log["args"]["price"],
                        "size": log["args"]["size"],
                        "fee": log["args"]["fee"],
                        "timestamp": block["timestamp"],
                        "blockNumber": log["blockNumber"],
                        "txHash": log["transactionHash"].hex(),
                    })
            except Exception as e:
                print(f"[RELAYER] Trade scan chunk {start}-{end}: {e}", flush=True)
                break  # Stop on error, retry next call

        if new_trades:
            _trade_cache.extend(new_trades)
            print(f"[RELAYER] Found {len(new_trades)} new on-chain trades", flush=True)

        _last_scanned_block = latest
        return _trade_cache

    except Exception as e:
        print(f"[RELAYER] get_onchain_trades error: {e}", flush=True)
        return _trade_cache
