#!/usr/bin/env python3
"""Generate deterministic Polymarket CLOB parity fixtures from the official Python v2 client.

Set PY_CLOB_CLIENT_V2_PATH when the client checkout is somewhere other than
/tmp/py-clob-client-v2.
"""

from __future__ import annotations

import dataclasses
import json
import os
import subprocess
import sys
from pathlib import Path


CLIENT_PATH = Path(os.environ.get("PY_CLOB_CLIENT_V2_PATH", "/tmp/py-clob-client-v2"))
sys.path.insert(0, str(CLIENT_PATH))

from py_clob_client_v2.config import get_contract_config
from py_clob_client_v2.constants import BYTES32_ZERO
from py_clob_client_v2.order_utils.exchange_order_builder_v2 import ExchangeOrderBuilderV2
from py_clob_client_v2.order_utils.model.order_data_v2 import order_to_json_v2, OrderDataV2
from py_clob_client_v2.order_utils.model.side import Side
from py_clob_client_v2.order_utils.model.signature_type_v2 import SignatureTypeV2
from py_clob_client_v2.order_builder.builder import OrderBuilder, ROUNDING_CONFIG
from py_clob_client_v2.order_builder.constants import BUY, SELL
from py_clob_client_v2.signer import Signer
from py_clob_client_v2.signing.hmac import build_hmac_signature
from py_clob_client_v2.signing.eip712 import (
    CLOB_DOMAIN_NAME,
    CLOB_VERSION,
    MSG_TO_SIGN,
    sign_clob_auth_message,
)


PRIVATE_KEY = "0x59c6995e998f97a5a0044976f094538f4f0eb6e095e3875038fc2b94efc60e77"
MAKER_PROXY = "0x1111111111111111111111111111111111111111"
TOKEN_ID = "1234567890123456789012345678901234567890123456789012345678901234"
API_SECRET = "c2VjcmV0LWtleS0zMi1ieXRlcy0xMjM0NTY="


def source_metadata(path: str) -> dict:
    return {
        "client": "Polymarket/py-clob-client-v2",
        "commit": source_commit(),
        "path": path,
    }


def source_commit() -> str:
    try:
        return subprocess.check_output(
            ["git", "-C", str(CLIENT_PATH), "rev-parse", "HEAD"],
            text=True,
        ).strip()
    except Exception:
        return "unknown"


CASES = [
    {
        "name": "polygon_standard_gnosis_safe_buy",
        "chain_id": 137,
        "neg_risk": False,
        "signature_type": SignatureTypeV2.POLY_GNOSIS_SAFE,
        "maker": MAKER_PROXY,
        "side": Side.BUY,
        "salt": "170000000000000000000000000000000000000001",
        "maker_amount": "5500000",
        "taker_amount": "10000000",
        "timestamp": "1712345678123",
        "expiration": "0",
        "metadata": BYTES32_ZERO,
        "builder": BYTES32_ZERO,
        "order_type": "GTC",
    },
    {
        "name": "polygon_neg_risk_gnosis_safe_sell",
        "chain_id": 137,
        "neg_risk": True,
        "signature_type": SignatureTypeV2.POLY_GNOSIS_SAFE,
        "maker": MAKER_PROXY,
        "side": Side.SELL,
        "salt": "170000000000000000000000000000000000000002",
        "maker_amount": "7500000",
        "taker_amount": "3375000",
        "timestamp": "1712345678456",
        "expiration": "0",
        "metadata": "0x" + "12" * 32,
        "builder": BYTES32_ZERO,
        "order_type": "FOK",
    },
    {
        "name": "amoy_standard_eoa_buy",
        "chain_id": 80002,
        "neg_risk": False,
        "signature_type": SignatureTypeV2.EOA,
        "maker": None,
        "side": Side.BUY,
        "salt": "170000000000000000000000000000000000000003",
        "maker_amount": "1250000",
        "taker_amount": "5000000",
        "timestamp": "1712345678789",
        "expiration": "0",
        "metadata": BYTES32_ZERO,
        "builder": "0x" + "34" * 32,
        "order_type": "GTC",
    },
]

BUILDER_AMOUNT_CASES = [
    {"name": "limit_buy_tick_0_01", "kind": "limit", "side": BUY, "size_or_amount": 21.04, "price": 0.56, "tick_size": "0.01"},
    {"name": "limit_sell_tick_0_001", "kind": "limit", "side": SELL, "size_or_amount": 21.04, "price": 0.056, "tick_size": "0.001"},
    {"name": "limit_buy_precision", "kind": "limit", "side": BUY, "size_or_amount": 20.0, "price": 0.82, "tick_size": "0.01"},
    {"name": "market_buy_tick_0_01", "kind": "market", "side": BUY, "size_or_amount": 21.04, "price": 0.56, "tick_size": "0.01"},
    {"name": "market_sell_tick_0_0001", "kind": "market", "side": SELL, "size_or_amount": 10, "price": 0.0056, "tick_size": "0.0001"},
]

BUILDER_ORDER_CASES = [
    {
        "name": "limit_buy_standard_gnosis_safe",
        "kind": "limit",
        "side": BUY,
        "size_or_amount": 21.04,
        "price": 0.56,
        "tick_size": "0.01",
        "neg_risk": False,
        "signature_type": SignatureTypeV2.POLY_GNOSIS_SAFE,
        "maker": MAKER_PROXY,
        "salt": "180000000000000000000000000000000000000001",
        "timestamp": "1712345680001",
        "expiration": "1234567",
        "metadata": BYTES32_ZERO,
        "builder": "0x" + "ab" * 32,
    },
    {
        "name": "limit_sell_neg_risk_gnosis_safe",
        "kind": "limit",
        "side": SELL,
        "size_or_amount": 21.04,
        "price": 0.056,
        "tick_size": "0.001",
        "neg_risk": True,
        "signature_type": SignatureTypeV2.POLY_GNOSIS_SAFE,
        "maker": MAKER_PROXY,
        "salt": "180000000000000000000000000000000000000002",
        "timestamp": "1712345680002",
        "expiration": "0",
        "metadata": "0x" + "12" * 32,
        "builder": BYTES32_ZERO,
    },
    {
        "name": "market_buy_standard_eoa",
        "kind": "market",
        "side": BUY,
        "size_or_amount": 21.04,
        "price": 0.56,
        "tick_size": "0.01",
        "neg_risk": False,
        "signature_type": SignatureTypeV2.EOA,
        "maker": None,
        "salt": "180000000000000000000000000000000000000003",
        "timestamp": "1712345680003",
        "expiration": "0",
        "metadata": BYTES32_ZERO,
        "builder": "0x" + "cd" * 32,
    },
]


def order_fixture(case: dict) -> dict:
    signer = Signer(PRIVATE_KEY, case["chain_id"])
    config = get_contract_config(case["chain_id"])
    contract = config.neg_risk_exchange_v2 if case["neg_risk"] else config.exchange_v2
    maker = case["maker"] or signer.address()

    builder = ExchangeOrderBuilderV2(
        contract_address=contract,
        chain_id=case["chain_id"],
        signer=signer,
        generate_salt=lambda: case["salt"],
    )

    data = OrderDataV2(
        maker=maker,
        signer=signer.address(),
        tokenId=TOKEN_ID,
        makerAmount=case["maker_amount"],
        takerAmount=case["taker_amount"],
        side=case["side"],
        signatureType=case["signature_type"],
        timestamp=case["timestamp"],
        metadata=case["metadata"],
        builder=case["builder"],
        expiration=case["expiration"],
    )

    order = builder.build_order(data)
    typed_data = builder.build_order_typed_data(order)
    signature = builder.build_order_signature(typed_data)
    order_hash = builder.build_order_hash(typed_data)
    signed = builder.build_signed_order(data)

    return {
        "name": case["name"],
        "source": source_metadata("py_clob_client_v2/order_utils/exchange_order_builder_v2.py"),
        "private_key": PRIVATE_KEY,
        "signer_address": signer.address(),
        "chain_id": case["chain_id"],
        "neg_risk": case["neg_risk"],
        "exchange_address": contract,
        "order_type": case["order_type"],
        "typed_data": typed_data,
        "unsigned_order": dataclasses.asdict(order),
        "signature": signature,
        "order_hash": order_hash,
        "signed_order": dataclasses.asdict(signed),
        "post_body": order_to_json_v2(
            signed,
            owner=maker,
            order_type=case["order_type"],
        ),
    }


def amount_fixture(case: dict) -> dict:
    signer = Signer(PRIVATE_KEY, 137)
    builder = OrderBuilder(signer)
    round_config = ROUNDING_CONFIG[case["tick_size"]]

    if case["kind"] == "limit":
        side, maker_amount, taker_amount = builder.get_order_amounts(
            case["side"], case["size_or_amount"], case["price"], round_config
        )
    else:
        side, maker_amount, taker_amount = builder.get_market_order_amounts(
            case["side"], case["size_or_amount"], case["price"], round_config
        )

    return {
        "name": case["name"],
        "kind": case["kind"],
        "source": source_metadata("py_clob_client_v2/order_builder/builder.py"),
        "input": {
            "side": case["side"],
            "size_or_amount": case["size_or_amount"],
            "price": case["price"],
            "tick_size": case["tick_size"],
        },
        "output": {
            "side": int(side),
            "maker_amount": str(maker_amount),
            "taker_amount": str(taker_amount),
        },
    }


def builder_order_fixture(case: dict) -> dict:
    signer = Signer(PRIVATE_KEY, 137)
    amount = amount_fixture(case)["output"]
    config = get_contract_config(137)
    contract = config.neg_risk_exchange_v2 if case["neg_risk"] else config.exchange_v2
    maker = case["maker"] or signer.address()

    data = OrderDataV2(
        maker=maker,
        signer=signer.address(),
        tokenId=TOKEN_ID,
        makerAmount=amount["maker_amount"],
        takerAmount=amount["taker_amount"],
        side=Side(int(amount["side"])),
        signatureType=case["signature_type"],
        timestamp=case["timestamp"],
        metadata=case["metadata"],
        builder=case["builder"],
        expiration=case["expiration"],
    )

    builder = ExchangeOrderBuilderV2(
        contract_address=contract,
        chain_id=137,
        signer=signer,
        generate_salt=lambda: case["salt"],
    )

    order = builder.build_order(data)
    typed_data = builder.build_order_typed_data(order)
    signed = builder.build_signed_order(data)

    return {
        "name": case["name"],
        "kind": case["kind"],
        "source": source_metadata("py_clob_client_v2/order_builder/builder.py"),
        "private_key": PRIVATE_KEY,
        "chain_id": 137,
        "exchange_address": contract,
        "input": {
            "side": case["side"],
            "size_or_amount": case["size_or_amount"],
            "price": case["price"],
            "tick_size": case["tick_size"],
            "neg_risk": case["neg_risk"],
            "signature_type": int(case["signature_type"]),
            "maker": maker,
            "salt": case["salt"],
            "timestamp": case["timestamp"],
            "expiration": case["expiration"],
            "metadata": case["metadata"],
            "builder": case["builder"],
        },
        "amounts": amount,
        "typed_data": typed_data,
        "unsigned_order": dataclasses.asdict(order),
        "signature": builder.build_order_signature(typed_data),
        "order_hash": builder.build_order_hash(typed_data),
        "signed_order": dataclasses.asdict(signed),
    }


def auth_fixture() -> dict:
    signer = Signer(PRIVATE_KEY, 137)
    timestamp = 1712345678
    nonce = 7

    return {
        "name": "polygon_clob_auth_nonce_7",
        "source": source_metadata("py_clob_client_v2/signing/eip712.py"),
        "private_key": PRIVATE_KEY,
        "address": signer.address(),
        "chain_id": 137,
        "timestamp": timestamp,
        "nonce": nonce,
        "domain": {
            "name": CLOB_DOMAIN_NAME,
            "version": CLOB_VERSION,
            "chainId": 137,
        },
        "message": {
            "address": signer.address(),
            "timestamp": str(timestamp),
            "nonce": nonce,
            "message": MSG_TO_SIGN,
        },
        "signature": sign_clob_auth_message(signer, timestamp, nonce),
        "headers": {
            "POLY_ADDRESS": signer.address(),
            "POLY_SIGNATURE": sign_clob_auth_message(signer, timestamp, nonce),
            "POLY_TIMESTAMP": str(timestamp),
            "POLY_NONCE": str(nonce),
        },
    }


def hmac_fixture() -> dict:
    timestamp = "1712345678"
    method = "POST"
    request_path = "/order"
    body = '{"order":{"tokenId":"123","side":"BUY"},"orderType":"GTC"}'

    return {
        "name": "l2_hmac_post_order",
        "source": source_metadata("py_clob_client_v2/signing/hmac.py"),
        "address": Signer(PRIVATE_KEY, 137).address(),
        "api_key": "fixture-api-key",
        "api_secret": API_SECRET,
        "api_passphrase": "fixture-passphrase",
        "timestamp": timestamp,
        "method": method,
        "request_path": request_path,
        "body": body,
        "signature": build_hmac_signature(API_SECRET, timestamp, method, request_path, body),
        "headers": {
            "POLY_ADDRESS": Signer(PRIVATE_KEY, 137).address(),
            "POLY_SIGNATURE": build_hmac_signature(
                API_SECRET, timestamp, method, request_path, body
            ),
            "POLY_TIMESTAMP": timestamp,
            "POLY_API_KEY": "fixture-api-key",
            "POLY_PASSPHRASE": "fixture-passphrase",
        },
    }


def write_json(path: Path, payload: dict) -> None:
    path.write_text(json.dumps(jsonable(payload), indent=2, sort_keys=True) + "\n")


def jsonable(value):
    if isinstance(value, bytes):
        return "0x" + value.hex()

    if isinstance(value, dict):
        return {key: jsonable(item) for key, item in value.items()}

    if isinstance(value, list):
        return [jsonable(item) for item in value]

    if hasattr(value, "__int__") and value.__class__.__name__.endswith("TypeV2"):
        return int(value)

    if hasattr(value, "__int__") and value.__class__.__name__ == "Side":
        return int(value)

    return value


def main() -> None:
    out_dir = Path(__file__).resolve().parents[1] / "test" / "fixtures" / "parity"
    out_dir.mkdir(parents=True, exist_ok=True)

    write_json(out_dir / "clob_auth.json", auth_fixture())
    write_json(out_dir / "l2_hmac.json", hmac_fixture())

    orders = [order_fixture(case) for case in CASES]
    write_json(
        out_dir / "orders.json",
        {
            "source": {
                "client": "Polymarket/py-clob-client-v2",
                "commit": source_commit(),
                "path": "py_clob_client_v2/order_utils/exchange_order_builder_v2.py",
            },
            "fixtures": orders,
        },
    )

    write_json(
        out_dir / "order_builder.json",
        {
            "source": source_metadata("py_clob_client_v2/order_builder/builder.py"),
            "amount_fixtures": [amount_fixture(case) for case in BUILDER_AMOUNT_CASES],
            "order_fixtures": [builder_order_fixture(case) for case in BUILDER_ORDER_CASES],
        },
    )


if __name__ == "__main__":
    main()
