"""
Turbo API client — shared by all MM scripts.
Connects to the local backend CLOB.
Signs orders with EIP-712 matching Turbo.sol exactly.
"""

import time
import requests
from eth_account import Account
from eth_abi import encode
from web3 import Web3

API_BASE = "http://localhost:8080"
PRICE_SCALE = 1_000_000


class TurboClient:
    def __init__(self, maker_address: str, base_url: str = API_BASE,
                 private_key: str = ""):
        self.maker = maker_address
        self.base = base_url
        self.private_key = private_key
        self._contract_address = None
        self._chain_id = None
        self._domain_separator = None
        self._order_typehash = None

        # Fetch config on init
        try:
            cfg = self.get_config()
            self._contract_address = cfg.get("contractAddress", "")
            self._chain_id = cfg.get("chainId", 143)
            if self._contract_address:
                self._compute_domain()
        except Exception as e:
            print(f"[CLIENT] Config fetch error: {e}")

    def _compute_domain(self):
        """Compute EIP-712 domain separator matching Turbo.sol exactly."""
        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")
        self._domain_separator = Web3.keccak(encode(
            ["bytes32", "bytes32", "bytes32", "uint256", "address"],
            [ds_typehash, name_hash, version_hash, self._chain_id, self._contract_address],
        ))
        self._order_typehash = Web3.keccak(
            text="Order(bytes32 marketId,address maker,uint8 side,uint8 outcome,uint256 price,uint256 size,uint256 nonce,uint256 expiration)"
        )

    def _sign_order(self, market_id: str, side: int, outcome: int,
                    price: int, size: int, nonce: int, expiration: int) -> str:
        """
        Sign an order with EIP-712, matching Turbo.sol hashOrder() exactly.
        Uses manual encoding — NOT eth_account.encode_typed_data (which differs).
        """
        if not self.private_key or not self._domain_separator:
            return ""

        # Pad marketId to bytes32
        mid = market_id if market_id.startswith("0x") else "0x" + market_id
        mid_bytes = bytes.fromhex(mid[2:].ljust(64, "0"))

        # Compute struct hash: keccak256(abi.encode(typehash, fields...))
        struct_hash = Web3.keccak(encode(
            ["bytes32", "bytes32", "address", "uint8", "uint8", "uint256", "uint256", "uint256", "uint256"],
            [self._order_typehash, mid_bytes, self.maker, side, outcome, price, size, nonce, expiration],
        ))

        # Compute digest: keccak256("\x19\x01" + domainSeparator + structHash)
        digest = Web3.keccak(b"\x19\x01" + self._domain_separator + struct_hash)

        # Sign the digest
        signed = Account.unsafe_sign_hash(digest, private_key=self.private_key)
        return signed.signature.hex()

    # ── Market ────────────────────────────────────────────────

    def get_market(self) -> dict:
        r = requests.get(f"{self.base}/api/markets/active")
        r.raise_for_status()
        return r.json()["markets"]["5"]

    def get_config(self) -> dict:
        r = requests.get(f"{self.base}/api/config")
        r.raise_for_status()
        return r.json()

    # ── Orderbook ─────────────────────────────────────────────

    def get_orderbook(self, market_id: str, outcome: str = "YES") -> dict:
        r = requests.get(f"{self.base}/api/orderbook/{market_id}?outcome={outcome}")
        r.raise_for_status()
        return r.json()

    # ── Orders ────────────────────────────────────────────────

    def place_order(self, side: int, outcome: int, price: int, size: int,
                    nonce: int = 0, expiration: int = 0) -> dict:
        if nonce == 0:
            nonce = int(time.time() * 1000)
        if expiration == 0:
            expiration = int(time.time()) + 3600

        # Get current market ID for signing
        mkt = self.get_market()
        market_id = mkt.get("marketId", "")

        # Sign the order
        signature = self._sign_order(market_id, side, outcome, price, size, nonce, expiration)

        r = requests.post(f"{self.base}/api/orders", json={
            "maker": self.maker,
            "side": side,
            "outcome": outcome,
            "price": price,
            "size": size,
            "nonce": nonce,
            "expiration": expiration,
            "signature": signature,
        })
        r.raise_for_status()
        return r.json()

    def amend_order(self, order_id: str, price: int = None, size: int = None) -> dict:
        body = {}
        if price is not None:
            body["price"] = price
        if size is not None:
            body["size"] = size
        r = requests.put(f"{self.base}/api/orders/{order_id}", json=body)
        r.raise_for_status()
        return r.json()

    def cancel_order(self, order_id: str) -> dict:
        r = requests.delete(f"{self.base}/api/orders/{order_id}")
        r.raise_for_status()
        return r.json()

    def cancel_all(self) -> int:
        orders = self.get_my_orders()
        count = 0
        for o in orders:
            try:
                self.cancel_order(o["id"])
                count += 1
            except Exception:
                pass
        return count

    def get_my_orders(self) -> list[dict]:
        r = requests.get(f"{self.base}/api/orders", params={"maker": self.maker})
        r.raise_for_status()
        return r.json().get("orders", [])

    # ── Positions ─────────────────────────────────────────────

    def get_position(self) -> dict:
        r = requests.get(f"{self.base}/api/positions/{self.maker}")
        r.raise_for_status()
        positions = r.json().get("positions", [])
        return positions[0] if positions else {"yesShares": 0, "noShares": 0, "totalInvested": 0}

    # ── History ───────────────────────────────────────────────

    def get_history(self) -> list[dict]:
        r = requests.get(f"{self.base}/api/history/{self.maker}")
        r.raise_for_status()
        return r.json().get("markets", [])

    def get_trades(self, market_id: str) -> list[dict]:
        r = requests.get(f"{self.base}/api/trades/{market_id}")
        r.raise_for_status()
        return r.json().get("trades", [])
