//! Polymarket CLOB exchange client.
//!
//! Implements the Exchange trait for Polymarket's Central Limit Order Book.
//! Uses API key authentication (HMAC-SHA256) for order management.
//! Uses EIP-712 typed data signing for on-chain order placement.
//! Includes fill polling to track executed trades.

use crate::error::HorizonError;
use crate::exchanges::Exchange;
use crate::types::{Fill, OrderRequest, OrderSide, Side};
use hmac::{Hmac, Mac};
use k256::ecdsa::{RecoveryId, Signature, SigningKey, VerifyingKey};
use sha2::Sha256;
use sha3::{Digest, Keccak256};
use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::{debug, error, info, warn};
use zeroize::Zeroizing;

type HmacSha256 = Hmac<Sha256>;

const DEFAULT_CLOB_URL: &str = "https://clob.polymarket.com";

/// Polymarket CTF Exchange contract address on Polygon (chainId=137).
const CTF_EXCHANGE: &str = "4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E";

/// Neg Risk CTF Exchange contract address on Polygon.
const NEG_RISK_CTF_EXCHANGE: &str = "C5d563A36AE78145C45a50134d48A1215220f80a";

/// USDC / Conditional Token decimals on Polygon (6 decimals = 1e6).
const DECIMALS: f64 = 1_000_000.0;

/// EIP-712 domain separator components.
const DOMAIN_NAME: &str = "Polymarket CTF Exchange";
const DOMAIN_VERSION: &str = "1";
const CHAIN_ID: u64 = 137;

pub struct PolymarketExchange {
    api_key: Zeroizing<String>,
    api_secret: Zeroizing<String>,
    api_passphrase: Zeroizing<String>,
    clob_url: String,
    client: reqwest::Client,
    runtime: Arc<tokio::runtime::Runtime>,
    fills: Mutex<Vec<Fill>>,
    next_fill_id: Mutex<u64>,
    /// Tracks the last fill timestamp we've seen, for incremental polling.
    last_fill_ts: Mutex<f64>,
    /// EIP-712 signing key (from private_key hex).
    signing_key: Option<SigningKey>,
    /// Ethereum address derived from the signing key (20 bytes).
    maker_address: [u8; 20],
    /// Tracks open order IDs per market for per-market cancellation.
    open_orders: Mutex<HashMap<String, Vec<String>>>,
    /// Tracks order_id → Side for correct fill side resolution.
    order_sides: Mutex<HashMap<String, Side>>,
    /// Precomputed EIP-712 domain separators (avoids 5 keccak256 hashes per order).
    domain_sep_ctf: [u8; 32],
    domain_sep_neg_risk: [u8; 32],
    /// Seen fill IDs for deduplication (prevents duplicate fills on same-timestamp trades).
    seen_fill_ids: Mutex<HashSet<String>>,
    seen_fill_order: Mutex<VecDeque<String>>,
}

// -- EIP-712 helpers ----------------------------------------------------------

/// keccak256 hash.
fn keccak256(data: &[u8]) -> [u8; 32] {
    let mut hasher = Keccak256::new();
    hasher.update(data);
    let result = hasher.finalize();
    let mut out = [0u8; 32];
    out.copy_from_slice(&result);
    out
}

/// Encode a u256 value from u64 into a 32-byte big-endian word.
fn u64_to_u256(v: u64) -> [u8; 32] {
    let mut buf = [0u8; 32];
    buf[24..32].copy_from_slice(&v.to_be_bytes());
    buf
}

/// Encode a u256 value from u128 into a 32-byte big-endian word.
fn u128_to_u256(v: u128) -> [u8; 32] {
    let mut buf = [0u8; 32];
    buf[16..32].copy_from_slice(&v.to_be_bytes());
    buf
}

/// Parse a decimal string (potentially very large) into a 32-byte u256.
/// Used for token IDs which are large uint256 values.
fn decimal_str_to_u256(s: &str) -> [u8; 32] {
    // Parse as u128 if it fits, otherwise do big-number arithmetic
    if let Ok(v) = s.parse::<u128>() {
        return u128_to_u256(v);
    }

    // For values > u128::MAX, do manual base-10 → base-256 conversion
    let mut result = [0u8; 32];
    for ch in s.bytes() {
        if !ch.is_ascii_digit() {
            continue;
        }
        let digit = (ch - b'0') as u16;
        // result = result * 10 + digit
        let mut carry = digit;
        for byte in result.iter_mut().rev() {
            let v = (*byte as u16) * 10 + carry;
            *byte = (v & 0xff) as u8;
            carry = v >> 8;
        }
    }
    result
}

/// Pad an address (20 bytes) to 32 bytes (left-padded with zeros).
fn address_to_u256(addr: &[u8; 20]) -> [u8; 32] {
    let mut buf = [0u8; 32];
    buf[12..32].copy_from_slice(addr);
    buf
}

/// Derive an Ethereum address from a secp256k1 signing key.
fn derive_address(key: &SigningKey) -> [u8; 20] {
    let verifying_key = VerifyingKey::from(key);
    let pubkey_bytes = verifying_key.to_encoded_point(false);
    // Uncompressed public key: 0x04 || x(32) || y(32)
    // Address = keccak256(x || y)[12..32]
    let hash = keccak256(&pubkey_bytes.as_bytes()[1..]);
    let mut addr = [0u8; 20];
    addr.copy_from_slice(&hash[12..32]);
    addr
}

/// Compute the EIP-712 domain separator.
///
///   hashStruct(EIP712Domain) = keccak256(
///     keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
///     keccak256(name), keccak256(version), chainId, verifyingContract
///   )
fn domain_separator(contract_addr: &[u8; 20]) -> [u8; 32] {
    let type_hash = keccak256(
        b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)",
    );
    let name_hash = keccak256(DOMAIN_NAME.as_bytes());
    let version_hash = keccak256(DOMAIN_VERSION.as_bytes());
    let chain_id = u64_to_u256(CHAIN_ID);
    let contract = address_to_u256(contract_addr);

    let mut buf = Vec::with_capacity(5 * 32);
    buf.extend_from_slice(&type_hash);
    buf.extend_from_slice(&name_hash);
    buf.extend_from_slice(&version_hash);
    buf.extend_from_slice(&chain_id);
    buf.extend_from_slice(&contract);

    keccak256(&buf)
}

/// Order type hash for the CTF Exchange Order struct.
///
///   keccak256("Order(uint256 salt,address maker,address signer,address taker,
///     uint256 tokenId,uint256 makerAmount,uint256 takerAmount,
///     uint256 expiration,uint256 nonce,uint256 feeRateBps,
///     uint8 side,uint8 signatureType)")
fn order_type_hash() -> [u8; 32] {
    keccak256(
        b"Order(uint256 salt,address maker,address signer,address taker,uint256 tokenId,uint256 makerAmount,uint256 takerAmount,uint256 expiration,uint256 nonce,uint256 feeRateBps,uint8 side,uint8 signatureType)",
    )
}

/// Struct representing a Polymarket CTF Exchange order for EIP-712 signing.
struct CtfOrder {
    salt: u128,
    maker: [u8; 20],
    signer: [u8; 20],
    taker: [u8; 20], // 0x0 for public orders
    token_id: [u8; 32],
    maker_amount: [u8; 32],
    taker_amount: [u8; 32],
    expiration: u64,
    nonce: u64,
    fee_rate_bps: u64,
    side: u8,            // 0 = BUY, 1 = SELL
    signature_type: u8,  // 0 = EOA
}

impl CtfOrder {
    /// Compute hashStruct(order).
    fn struct_hash(&self) -> [u8; 32] {
        let type_hash = order_type_hash();
        // 12 fields × 32 bytes each + type_hash
        let mut buf = Vec::with_capacity(13 * 32);
        buf.extend_from_slice(&type_hash);
        buf.extend_from_slice(&u128_to_u256(self.salt));
        buf.extend_from_slice(&address_to_u256(&self.maker));
        buf.extend_from_slice(&address_to_u256(&self.signer));
        buf.extend_from_slice(&address_to_u256(&self.taker));
        buf.extend_from_slice(&self.token_id);
        buf.extend_from_slice(&self.maker_amount);
        buf.extend_from_slice(&self.taker_amount);
        buf.extend_from_slice(&u64_to_u256(self.expiration));
        buf.extend_from_slice(&u64_to_u256(self.nonce));
        buf.extend_from_slice(&u64_to_u256(self.fee_rate_bps));
        buf.extend_from_slice(&u64_to_u256(self.side as u64));
        buf.extend_from_slice(&u64_to_u256(self.signature_type as u64));
        keccak256(&buf)
    }

    /// Compute the full EIP-712 digest: keccak256("\x19\x01" || domainSeparator || structHash).
    fn eip712_digest(&self, domain_sep: &[u8; 32]) -> [u8; 32] {
        let struct_hash = self.struct_hash();
        let mut buf = Vec::with_capacity(2 + 32 + 32);
        buf.push(0x19);
        buf.push(0x01);
        buf.extend_from_slice(domain_sep);
        buf.extend_from_slice(&struct_hash);
        keccak256(&buf)
    }
}

/// Sign the EIP-712 digest and return a 65-byte signature (r || s || v).
fn sign_digest(key: &SigningKey, digest: &[u8; 32]) -> Result<Vec<u8>, HorizonError> {
    let (signature, recovery_id): (Signature, RecoveryId) = key
        .sign_prehash_recoverable(digest)
        .map_err(|e| HorizonError::Exchange(format!("polymarket: ECDSA sign failed: {}", e)))?;

    let mut sig_bytes = Vec::with_capacity(65);
    sig_bytes.extend_from_slice(&signature.to_bytes());
    // v = recovery_id + 27
    sig_bytes.push(recovery_id.to_byte() + 27);
    Ok(sig_bytes)
}

// -- PolymarketExchange -------------------------------------------------------

impl PolymarketExchange {
    pub fn new(
        api_key: String,
        api_secret: String,
        api_passphrase: String,
        private_key: Option<String>,
        clob_url: Option<String>,
        runtime: Arc<tokio::runtime::Runtime>,
    ) -> Self {
        let (signing_key, maker_address) = match private_key {
            Some(ref pk) if !pk.is_empty() => {
                match parse_private_key(pk) {
                    Ok(sk) => {
                        let addr = derive_address(&sk);
                        info!(
                            address = %format!("0x{}", hex::encode(addr)),
                            "polymarket: loaded signing key"
                        );
                        (Some(sk), addr)
                    }
                    Err(e) => {
                        warn!(error = %e, "polymarket: invalid private key, order signing disabled");
                        (None, [0u8; 20])
                    }
                }
            }
            _ => {
                debug!("polymarket: no private key provided, order signing disabled");
                (None, [0u8; 20])
            }
        };

        // Precompute domain separators at construction time (avoids 5 keccak256 per order)
        let ctf_bytes = hex::decode(CTF_EXCHANGE).expect("invalid CTF_EXCHANGE hex");
        let mut ctf_addr = [0u8; 20];
        ctf_addr.copy_from_slice(&ctf_bytes);
        let domain_sep_ctf = domain_separator(&ctf_addr);

        let neg_bytes = hex::decode(NEG_RISK_CTF_EXCHANGE).expect("invalid NEG_RISK_CTF_EXCHANGE hex");
        let mut neg_addr = [0u8; 20];
        neg_addr.copy_from_slice(&neg_bytes);
        let domain_sep_neg_risk = domain_separator(&neg_addr);

        Self {
            api_key: Zeroizing::new(api_key),
            api_secret: Zeroizing::new(api_secret),
            api_passphrase: Zeroizing::new(api_passphrase),
            clob_url: clob_url.unwrap_or_else(|| DEFAULT_CLOB_URL.to_string()),
            client: reqwest::Client::builder()
                .timeout(std::time::Duration::from_secs(10))
                .connect_timeout(std::time::Duration::from_secs(5))
                .build()
                .unwrap_or_else(|_| reqwest::Client::new()),
            runtime,
            fills: Mutex::new(Vec::new()),
            next_fill_id: Mutex::new(1),
            last_fill_ts: Mutex::new(0.0),
            signing_key,
            maker_address,
            open_orders: Mutex::new(HashMap::new()),
            order_sides: Mutex::new(HashMap::new()),
            seen_fill_ids: Mutex::new(HashSet::new()),
            seen_fill_order: Mutex::new(VecDeque::new()),
            domain_sep_ctf,
            domain_sep_neg_risk,
        }
    }

    /// Generate HMAC-SHA256 signature for Polymarket API auth.
    fn sign_hmac(
        &self,
        timestamp: &str,
        method: &str,
        path: &str,
        body: &str,
    ) -> Result<String, HorizonError> {
        let message = format!("{}{}{}{}", timestamp, method, path, body);
        let secret_bytes =
            base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &*self.api_secret)
                .map_err(|_| {
                    HorizonError::Exchange(
                        "polymarket: invalid api_secret (bad base64)".to_string(),
                    )
                })?;

        let mut mac = HmacSha256::new_from_slice(&secret_bytes).map_err(|e| {
            HorizonError::Exchange(format!("polymarket: HMAC key error: {}", e))
        })?;
        hmac::Mac::update(&mut mac, message.as_bytes());
        let result = mac.finalize();
        Ok(base64::Engine::encode(
            &base64::engine::general_purpose::STANDARD,
            result.into_bytes(),
        ))
    }

    /// Build authenticated headers for a request.
    fn auth_headers(
        &self,
        method: &str,
        path: &str,
        body: &str,
    ) -> Result<Vec<(String, String)>, HorizonError> {
        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs()
            .to_string();

        let signature = self.sign_hmac(&timestamp, method, path, body)?;

        Ok(vec![
            ("POLY-API-KEY".to_string(), (*self.api_key).clone()),
            ("POLY-SIGNATURE".to_string(), signature),
            ("POLY-TIMESTAMP".to_string(), timestamp),
            ("POLY-PASSPHRASE".to_string(), (*self.api_passphrase).clone()),
        ])
    }

    /// Convert OrderSide to Polymarket API string.
    fn poly_side_str(order_side: OrderSide) -> &'static str {
        match order_side {
            OrderSide::Buy => "BUY",
            OrderSide::Sell => "SELL",
        }
    }

    /// Convert OrderSide to on-chain uint8 (BUY=0, SELL=1).
    fn poly_side_u8(order_side: OrderSide) -> u8 {
        match order_side {
            OrderSide::Buy => 0,
            OrderSide::Sell => 1,
        }
    }

    /// Map TimeInForce to Polymarket string.
    fn poly_tif(tif: crate::types::TimeInForce) -> &'static str {
        match tif {
            crate::types::TimeInForce::GTC => "GTC",
            crate::types::TimeInForce::GTD => "GTD",
            crate::types::TimeInForce::FOK => "FOK",
            crate::types::TimeInForce::FAK => "FAK",
        }
    }

    /// Determine Polymarket orderType based on OrderType and TimeInForce.
    /// Market orders use FOK (Polymarket's fill-or-kill = immediate execution).
    fn poly_order_type(req: &OrderRequest) -> &'static str {
        match req.order_type {
            crate::types::OrderType::Market => "FOK",
            crate::types::OrderType::Limit => Self::poly_tif(req.time_in_force),
        }
    }

    /// Build a signed order body for the CLOB API.
    ///
    /// Returns the JSON body with the EIP-712 signed order.
    fn build_signed_order(
        &self,
        req: &OrderRequest,
        neg_risk: bool,
    ) -> Result<serde_json::Value, HorizonError> {
        let signing_key = self.signing_key.as_ref().ok_or_else(|| {
            HorizonError::Exchange(
                "polymarket: cannot submit signed orders without a private key".into(),
            )
        })?;

        if !req.price.is_finite() || !req.size.is_finite() {
            return Err(HorizonError::Exchange(
                "polymarket: price and size must be finite".into(),
            ));
        }

        // Generate a unique salt: timestamp_nanos XOR a counter
        let salt: u128 = {
            let ts = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap_or_default()
                .as_nanos();
            let counter = {
                let mut c = self.next_fill_id.lock().unwrap_or_else(|e| e.into_inner());
                let v = *c;
                *c += 1;
                v
            };
            ts ^ (counter as u128).wrapping_shl(64)
        };

        // Use token_id if set, otherwise fall back to market_id
        let token_id_str = req.token_id.as_deref().unwrap_or(&req.market_id);
        let token_id_u256 = decimal_str_to_u256(token_id_str);

        // Calculate amounts in 6 decimal places
        // BUY: maker gives USDC (price * size), taker gives CT (size)
        // SELL: maker gives CT (size), taker gives USDC (price * size)
        let raw_price_amount = (req.price * req.size * DECIMALS).round() as u128;
        let raw_size_amount = (req.size * DECIMALS).round() as u128;

        let (maker_amount, taker_amount) = match req.order_side {
            OrderSide::Buy => (u128_to_u256(raw_price_amount), u128_to_u256(raw_size_amount)),
            OrderSide::Sell => (u128_to_u256(raw_size_amount), u128_to_u256(raw_price_amount)),
        };

        let order = CtfOrder {
            salt,
            maker: self.maker_address,
            signer: self.maker_address,
            taker: [0u8; 20],
            token_id: token_id_u256,
            maker_amount,
            taker_amount,
            expiration: 0,
            nonce: 0,
            fee_rate_bps: 0,
            side: Self::poly_side_u8(req.order_side),
            signature_type: 0, // EOA
        };

        // Use precomputed domain separator based on neg_risk
        let domain_sep = if neg_risk {
            &self.domain_sep_neg_risk
        } else {
            &self.domain_sep_ctf
        };
        let digest = order.eip712_digest(domain_sep);
        let signature = sign_digest(signing_key, &digest)?;
        let sig_hex = format!("0x{}", hex::encode(&signature));

        let maker_hex = format!("0x{}", hex::encode(self.maker_address));

        // JSON body amounts must match the signed EIP-712 struct (side-dependent)
        let (json_maker_amount, json_taker_amount) = match req.order_side {
            OrderSide::Buy => (raw_price_amount, raw_size_amount),
            OrderSide::Sell => (raw_size_amount, raw_price_amount),
        };

        // Build CLOB API order body
        let body = serde_json::json!({
            "order": {
                "salt": salt.to_string(),
                "maker": maker_hex,
                "signer": maker_hex,
                "taker": "0x0000000000000000000000000000000000000000",
                "tokenID": token_id_str,
                "makerAmount": json_maker_amount.to_string(),
                "takerAmount": json_taker_amount.to_string(),
                "expiration": "0",
                "nonce": "0",
                "feeRateBps": "0",
                "side": Self::poly_side_str(req.order_side),
                "signatureType": 0,
                "signature": sig_hex,
            },
            "owner": maker_hex,
            "orderType": Self::poly_order_type(req),
        });

        Ok(body)
    }

    /// Submit order to Polymarket CLOB with exponential backoff on 429.
    async fn submit_order_async(&self, req: &OrderRequest) -> Result<String, HorizonError> {
        let path = "/order";

        let body = self.build_signed_order(req, req.neg_risk)?;
        let body_str = body.to_string();
        let url = format!("{}{}", self.clob_url, path);

        debug!(
            market = %req.market_id,
            side = ?req.order_side,
            price = req.price,
            size = req.size,
            signed = self.signing_key.is_some(),
            "polymarket: submitting order"
        );

        let mut backoff_ms: u64 = 1000;
        const MAX_RETRIES: u32 = 4;

        for attempt in 0..=MAX_RETRIES {
            let headers = self.auth_headers("POST", path, &body_str)?;
            let mut request = self.client.post(&url).json(&body);
            for (key, value) in &headers {
                request = request.header(key, value);
            }

            let resp = request
                .send()
                .await
                .map_err(|e| HorizonError::Exchange(format!("polymarket request failed: {}", e)))?;

            if resp.status().as_u16() == 429 && attempt < MAX_RETRIES {
                warn!(attempt = attempt + 1, backoff_ms = backoff_ms, "polymarket: rate limited (429), retrying");
                tokio::time::sleep(tokio::time::Duration::from_millis(backoff_ms)).await;
                backoff_ms = (backoff_ms * 2).min(30_000);
                continue;
            }

            if !resp.status().is_success() {
                let status = resp.status();
                let body_text = resp.text().await.unwrap_or_default();
                error!(
                    status = %status,
                    body = %body_text,
                    "polymarket: order rejected"
                );
                return Err(HorizonError::Exchange(format!(
                    "polymarket order rejected ({}): {}",
                    status, body_text
                )));
            }

            let json: serde_json::Value = resp
                .json()
                .await
                .map_err(|e| HorizonError::Exchange(format!("polymarket parse error: {}", e)))?;

            let order_id = json
                .get("orderID")
                .or_else(|| json.get("id"))
                .and_then(|v| v.as_str())
                .filter(|s| !s.is_empty())
                .map(|s| s.to_string())
                .ok_or_else(|| {
                    HorizonError::Exchange(format!(
                        "polymarket: response missing order ID: {}",
                        json
                    ))
                })?;

            if attempt > 0 {
                info!(order_id = %order_id, attempts = attempt + 1, "polymarket: order accepted (after retry)");
            } else {
                info!(order_id = %order_id, "polymarket: order accepted");
            }
            return Ok(order_id);
        }

        Err(HorizonError::Exchange("polymarket: rate limit retries exhausted".into()))
    }

    /// Cancel order on Polymarket.
    async fn cancel_order_async(&self, order_id: &str) -> Result<(), HorizonError> {
        let path = "/order";
        let body = serde_json::json!({
            "orderID": order_id,
        });
        let body_str = body.to_string();

        let headers = self.auth_headers("DELETE", path, &body_str)?;
        let url = format!("{}{}", self.clob_url, path);

        let mut request = self.client.delete(&url).json(&body);
        for (key, value) in &headers {
            request = request.header(key, value);
        }

        let resp = request
            .send()
            .await
            .map_err(|e| HorizonError::Exchange(format!("polymarket cancel failed: {}", e)))?;

        if !resp.status().is_success() {
            let status = resp.status();
            let body = resp.text().await.unwrap_or_default();
            warn!(
                order_id = %order_id,
                status = %status,
                "polymarket: cancel failed"
            );
            return Err(HorizonError::Exchange(format!(
                "polymarket cancel rejected ({}): {}",
                status, body
            )));
        }

        debug!(order_id = %order_id, "polymarket: order canceled");
        Ok(())
    }

    /// Cancel all orders on Polymarket.
    async fn cancel_all_async(&self) -> Result<usize, HorizonError> {
        let path = "/cancel-all";
        let body_str = "{}";

        let headers = self.auth_headers("DELETE", path, body_str)?;
        let url = format!("{}{}", self.clob_url, path);

        let mut request = self
            .client
            .delete(&url)
            .header("Content-Type", "application/json")
            .body(body_str.to_string());
        for (key, value) in &headers {
            request = request.header(key, value);
        }

        let resp = request
            .send()
            .await
            .map_err(|e| HorizonError::Exchange(format!("polymarket cancel-all failed: {}", e)))?;

        if !resp.status().is_success() {
            let status = resp.status();
            let body = resp.text().await.unwrap_or_default();
            return Err(HorizonError::Exchange(format!(
                "polymarket cancel-all failed ({}): {}",
                status, body
            )));
        }

        let json: serde_json::Value = resp.json().await.unwrap_or_else(|e| {
            warn!(error = %e, "polymarket: cancel-all response parse failed");
            serde_json::Value::Null
        });
        let count = json
            .get("canceled")
            .and_then(|v| v.as_u64())
            .unwrap_or(0);

        info!(count = count, "polymarket: canceled all orders");
        Ok(count as usize)
    }

    /// Poll for recent trades/fills from Polymarket.
    async fn poll_fills_async(&self) -> Result<Vec<Fill>, HorizonError> {
        let path = "/trades";
        let headers = self.auth_headers("GET", path, "")?;
        let url = format!("{}{}", self.clob_url, path);

        let mut request = self.client.get(&url);
        for (key, value) in &headers {
            request = request.header(key, value);
        }

        let resp = request.send().await.map_err(|e| {
            HorizonError::Exchange(format!("polymarket: fill poll failed: {}", e))
        })?;

        if !resp.status().is_success() {
            let status = resp.status();
            debug!(status = %status, "polymarket: fill poll returned non-200");
            return Ok(Vec::new());
        }

        let json: serde_json::Value = resp.json().await.map_err(|e| {
            HorizonError::Exchange(format!("polymarket: fill parse error: {}", e))
        })?;

        let last_ts = {
            let ts = self.last_fill_ts.lock().unwrap_or_else(|e| e.into_inner());
            *ts
        };

        let mut new_fills = Vec::new();
        let mut max_ts = last_ts;

        let trades = json
            .as_array()
            .or_else(|| json.get("trades").and_then(|v| v.as_array()));

        if let Some(trades) = trades {
            let mut next_id = self.next_fill_id.lock().unwrap_or_else(|e| e.into_inner());

            for trade in trades {
                let ts = trade
                    .get("matchTime")
                    .or_else(|| trade.get("timestamp"))
                    .and_then(|v| v.as_f64().or_else(|| v.as_str().and_then(|s| s.parse().ok())))
                    .unwrap_or(0.0);

                if ts < last_ts {
                    continue;
                }

                let trade_id = trade
                    .get("tradeID")
                    .or_else(|| trade.get("id"))
                    .and_then(|v| v.as_str())
                    .unwrap_or("");

                // Dedup by fill ID for same-timestamp trades
                {
                    let mut seen = self.seen_fill_ids.lock().unwrap_or_else(|e| e.into_inner());
                    let dedup_key = if trade_id.is_empty() {
                        format!("{}_{}", ts, next_id)
                    } else {
                        trade_id.to_string()
                    };
                    if seen.contains(&dedup_key) {
                        continue;
                    }
                    seen.insert(dedup_key.clone());
                    // Track insertion order for FIFO eviction
                    let mut order = self.seen_fill_order.lock().unwrap_or_else(|e| e.into_inner());
                    order.push_back(dedup_key);
                    // Evict oldest entries when cap exceeded
                    while seen.len() > 10_000 {
                        if let Some(oldest) = order.pop_front() {
                            seen.remove(&oldest);
                        } else {
                            break;
                        }
                    }
                }

                if ts > max_ts {
                    max_ts = ts;
                }

                let order_id = trade
                    .get("orderID")
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string();

                let market_id = trade
                    .get("asset_id")
                    .or_else(|| trade.get("tokenID"))
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string();

                let price = trade
                    .get("price")
                    .and_then(|v| v.as_f64().or_else(|| v.as_str().and_then(|s| s.parse().ok())))
                    .unwrap_or(0.0);

                let size = trade
                    .get("size")
                    .and_then(|v| v.as_f64().or_else(|| v.as_str().and_then(|s| s.parse().ok())))
                    .unwrap_or(0.0);

                let fee = trade
                    .get("fee")
                    .and_then(|v| v.as_f64().or_else(|| v.as_str().and_then(|s| s.parse().ok())))
                    .unwrap_or(0.0);

                let side_str = trade.get("side").and_then(|v| v.as_str()).unwrap_or("BUY");
                let order_side = if side_str.eq_ignore_ascii_case("BUY") {
                    OrderSide::Buy
                } else {
                    OrderSide::Sell
                };

                let fill_id = if trade_id.is_empty() {
                    let id = format!("poly_fill_{}", *next_id);
                    *next_id += 1;
                    id
                } else {
                    trade_id.to_string()
                };

                // Resolve side from tracked order sides, fallback to Side::Yes
                let side = {
                    let sides = self.order_sides.lock().unwrap_or_else(|e| e.into_inner());
                    sides.get(&order_id).copied().unwrap_or(Side::Yes)
                };

                new_fills.push(Fill {
                    fill_id,
                    order_id,
                    market_id,
                    side,
                    order_side,
                    price,
                    size,
                    fee,
                    timestamp: ts,
                    token_id: trade
                        .get("asset_id")
                        .and_then(|v| v.as_str())
                        .map(|s| s.to_string()),
                    exchange: "polymarket".to_string(),
                    is_maker: false,
                });
            }
        }

        if max_ts > last_ts {
            if let Ok(mut ts) = self.last_fill_ts.lock() {
                *ts = max_ts;
            }
        }

        if !new_fills.is_empty() {
            info!(count = new_fills.len(), "polymarket: polled new fills");
        }

        Ok(new_fills)
    }

    /// Fetch positions from Polymarket for reconciliation.
    async fn fetch_positions_async(&self) -> Result<Vec<crate::types::Position>, HorizonError> {
        let path = "/positions";
        let headers = self.auth_headers("GET", path, "")?;
        let url = format!("{}{}", self.clob_url, path);

        let mut request = self.client.get(&url);
        for (key, value) in &headers {
            request = request.header(key, value);
        }

        let resp = request.send().await.map_err(|e| {
            HorizonError::Exchange(format!("polymarket: position fetch failed: {}", e))
        })?;

        if !resp.status().is_success() {
            let status = resp.status();
            debug!(status = %status, "polymarket: position fetch returned non-200");
            return Ok(Vec::new());
        }

        let json: serde_json::Value = resp.json().await.map_err(|e| {
            HorizonError::Exchange(format!("polymarket: position parse error: {}", e))
        })?;

        let mut positions = Vec::new();
        let arr = json.as_array().or_else(|| json.get("positions").and_then(|v| v.as_array()));

        if let Some(arr) = arr {
            for p in arr {
                let asset_id = p.get("asset_id").and_then(|v| v.as_str()).unwrap_or("");
                let size = p
                    .get("size")
                    .and_then(|v| v.as_f64().or_else(|| v.as_str().and_then(|s| s.parse().ok())))
                    .unwrap_or(0.0);
                let avg_price = p
                    .get("avgPrice")
                    .or_else(|| p.get("avg_price"))
                    .and_then(|v| v.as_f64().or_else(|| v.as_str().and_then(|s| s.parse().ok())))
                    .unwrap_or(0.0);

                if size.abs() > 0.0 {
                    // Determine YES vs NO from response
                    let outcome_str = p.get("outcome")
                        .or_else(|| p.get("side"))
                        .and_then(|v| v.as_str())
                        .unwrap_or("");
                    let side = if outcome_str.eq_ignore_ascii_case("no") {
                        Side::No
                    } else {
                        // Default to YES if indeterminate
                        if !outcome_str.is_empty() && !outcome_str.eq_ignore_ascii_case("yes") {
                            warn!(outcome = %outcome_str, "polymarket: unknown position outcome, defaulting to YES");
                        }
                        Side::Yes
                    };
                    positions.push(crate::types::Position {
                        market_id: asset_id.to_string(),
                        side,
                        size,
                        avg_entry_price: avg_price,
                        realized_pnl: 0.0,
                        unrealized_pnl: 0.0,
                        token_id: Some(asset_id.to_string()),
                        exchange: "polymarket".to_string(),
                    });
                }
            }
        }

        if !positions.is_empty() {
            info!(count = positions.len(), "polymarket: fetched positions for reconciliation");
        }

        Ok(positions)
    }
}

/// Parse a private key from hex string (with or without "0x" prefix).
fn parse_private_key(hex_str: &str) -> Result<SigningKey, HorizonError> {
    let clean = hex_str.strip_prefix("0x").unwrap_or(hex_str);
    let bytes = hex::decode(clean).map_err(|e| {
        HorizonError::Exchange(format!("polymarket: invalid private key hex: {}", e))
    })?;
    SigningKey::from_bytes((&bytes[..]).into()).map_err(|e| {
        HorizonError::Exchange(format!("polymarket: invalid private key: {}", e))
    })
}

impl Exchange for PolymarketExchange {
    fn submit_order(&self, req: &OrderRequest) -> Result<String, HorizonError> {
        let req_clone = req.clone();
        let order_id = self.runtime.block_on(self.submit_order_async(&req_clone))?;

        // Track order_id per market for per-market cancellation
        if let Ok(mut map) = self.open_orders.lock() {
            map.entry(req.market_id.clone())
                .or_default()
                .push(order_id.clone());
        }
        // Track order_id → side for fill side resolution
        if let Ok(mut map) = self.order_sides.lock() {
            map.insert(order_id.clone(), req.side);
        }

        Ok(order_id)
    }

    fn cancel_order(&self, order_id: &str) -> Result<(), HorizonError> {
        let id = order_id.to_string();
        self.runtime.block_on(self.cancel_order_async(&id))?;

        // Remove from open_orders tracking
        if let Ok(mut map) = self.open_orders.lock() {
            for ids in map.values_mut() {
                ids.retain(|oid| oid != order_id);
            }
        }
        // Clean up order_sides for canceled order
        if let Ok(mut sides) = self.order_sides.lock() {
            sides.remove(order_id);
        }

        Ok(())
    }

    fn cancel_all(&self) -> Result<usize, HorizonError> {
        let count = self.runtime.block_on(self.cancel_all_async())?;

        // Clear all tracked orders
        if let Ok(mut map) = self.open_orders.lock() {
            map.clear();
        }
        // Clear all order_sides tracking
        if let Ok(mut sides) = self.order_sides.lock() {
            sides.clear();
        }

        Ok(count)
    }

    fn cancel_for_market(&self, market_id: &str) -> Result<usize, HorizonError> {
        let order_ids: Vec<String> = {
            let map = self.open_orders.lock().unwrap_or_else(|e| e.into_inner());
            map.get(market_id).cloned().unwrap_or_default()
        };

        if order_ids.is_empty() {
            return Ok(0);
        }

        let mut successfully_canceled = Vec::new();
        for oid in &order_ids {
            match self.runtime.block_on(self.cancel_order_async(oid)) {
                Ok(()) => successfully_canceled.push(oid.clone()),
                Err(e) => {
                    debug!(order_id = %oid, error = %e, "polymarket: cancel_for_market: order cancel failed (may already be filled)");
                }
            }
        }

        // Only remove successfully canceled IDs from tracking
        if let Ok(mut map) = self.open_orders.lock() {
            if let Some(ids) = map.get_mut(market_id) {
                ids.retain(|oid| !successfully_canceled.contains(oid));
                if ids.is_empty() {
                    map.remove(market_id);
                }
            }
        }

        let canceled = successfully_canceled.len();
        info!(market_id = %market_id, canceled = canceled, total = order_ids.len(), "polymarket: canceled orders for market");
        Ok(canceled)
    }

    fn drain_fills(&self) -> Vec<Fill> {
        match self.runtime.block_on(self.poll_fills_async()) {
            Ok(new_fills) => {
                if !new_fills.is_empty() {
                    // Remove filled order IDs from open_orders tracking
                    if let Ok(mut map) = self.open_orders.lock() {
                        for fill in &new_fills {
                            for ids in map.values_mut() {
                                ids.retain(|oid| oid != &fill.order_id);
                            }
                        }
                        map.retain(|_, ids| !ids.is_empty());
                    }
                    // Clean up order_sides for filled orders
                    if let Ok(mut sides) = self.order_sides.lock() {
                        for fill in &new_fills {
                            sides.remove(&fill.order_id);
                        }
                    }
                    if let Ok(mut fills) = self.fills.lock() {
                        fills.extend(new_fills);
                    }
                }
            }
            Err(e) => {
                warn!(error = %e, "polymarket: fill polling error");
            }
        }

        match self.fills.lock() {
            Ok(mut fills) => std::mem::take(&mut *fills),
            Err(_) => {
                error!("polymarket: fills lock poisoned");
                Vec::new()
            }
        }
    }

    fn name(&self) -> &str {
        "polymarket"
    }

    fn fetch_positions(&self) -> Result<Vec<crate::types::Position>, HorizonError> {
        self.runtime.block_on(self.fetch_positions_async())
    }
}

// -- Tests --------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_keccak256() {
        let hash = keccak256(b"hello");
        assert_eq!(
            hex::encode(hash),
            "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"
        );
    }

    #[test]
    fn test_u64_to_u256() {
        let result = u64_to_u256(137);
        assert_eq!(result[31], 137);
        assert!(result[..31].iter().all(|&b| b == 0));
    }

    #[test]
    fn test_decimal_str_to_u256_small() {
        let result = decimal_str_to_u256("1000000");
        let expected = u128_to_u256(1_000_000);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_address_to_u256() {
        let mut addr = [0u8; 20];
        addr[19] = 0x42;
        let result = address_to_u256(&addr);
        assert_eq!(result[31], 0x42);
        assert!(result[..31].iter().all(|&b| b == 0));
    }

    #[test]
    fn test_derive_address() {
        // Known test vector: private key 1 -> known address
        let key_bytes = hex::decode(
            "0000000000000000000000000000000000000000000000000000000000000001",
        )
        .unwrap();
        let sk = SigningKey::from_bytes((&key_bytes[..]).into()).unwrap();
        let addr = derive_address(&sk);
        let addr_hex = hex::encode(addr);
        // Private key 1 -> 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf
        assert_eq!(addr_hex, "7e5f4552091a69125d5dfcb7b8c2659029395bdf");
    }

    #[test]
    fn test_domain_separator_deterministic() {
        let contract_bytes = hex::decode(CTF_EXCHANGE).unwrap();
        let mut contract_addr = [0u8; 20];
        contract_addr.copy_from_slice(&contract_bytes);

        let ds1 = domain_separator(&contract_addr);
        let ds2 = domain_separator(&contract_addr);
        assert_eq!(ds1, ds2);
        // Must not be all zeros
        assert!(ds1.iter().any(|&b| b != 0));
    }

    #[test]
    fn test_order_struct_hash_deterministic() {
        let order = CtfOrder {
            salt: 12345,
            maker: [0x42; 20],
            signer: [0x42; 20],
            taker: [0u8; 20],
            token_id: decimal_str_to_u256("100"),
            maker_amount: u128_to_u256(500_000),
            taker_amount: u128_to_u256(1_000_000),
            expiration: 0,
            nonce: 0,
            fee_rate_bps: 0,
            side: 0,
            signature_type: 0,
        };

        let h1 = order.struct_hash();
        let h2 = order.struct_hash();
        assert_eq!(h1, h2);
        assert!(h1.iter().any(|&b| b != 0));
    }

    #[test]
    fn test_sign_digest_produces_65_bytes() {
        let key_bytes = hex::decode(
            "0000000000000000000000000000000000000000000000000000000000000001",
        )
        .unwrap();
        let sk = SigningKey::from_bytes((&key_bytes[..]).into()).unwrap();
        let digest = keccak256(b"test message");
        let sig = sign_digest(&sk, &digest).unwrap();
        assert_eq!(sig.len(), 65);
        // v should be 27 or 28
        assert!(sig[64] == 27 || sig[64] == 28);
    }

    #[test]
    fn test_parse_private_key_with_prefix() {
        let sk = parse_private_key(
            "0x0000000000000000000000000000000000000000000000000000000000000001",
        );
        assert!(sk.is_ok());
    }

    #[test]
    fn test_parse_private_key_without_prefix() {
        let sk = parse_private_key(
            "0000000000000000000000000000000000000000000000000000000000000001",
        );
        assert!(sk.is_ok());
    }

    #[test]
    fn test_parse_private_key_invalid() {
        let sk = parse_private_key("not_hex");
        assert!(sk.is_err());
    }

    #[test]
    fn test_eip712_full_sign_round_trip() {
        // Test that we can build, hash, and sign an order end-to-end
        let key_bytes = hex::decode(
            "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
        )
        .unwrap();
        let sk = SigningKey::from_bytes((&key_bytes[..]).into()).unwrap();
        let addr = derive_address(&sk);

        let order = CtfOrder {
            salt: 1,
            maker: addr,
            signer: addr,
            taker: [0u8; 20],
            token_id: decimal_str_to_u256(
                "52114319501245915516055106046884209969926127482827954674443846427813813222426",
            ),
            maker_amount: u128_to_u256(50_000_000), // 50 USDC
            taker_amount: u128_to_u256(100_000_000), // 100 CT
            expiration: 0,
            nonce: 0,
            fee_rate_bps: 0,
            side: 0,
            signature_type: 0,
        };

        let contract_bytes = hex::decode(CTF_EXCHANGE).unwrap();
        let mut contract_addr = [0u8; 20];
        contract_addr.copy_from_slice(&contract_bytes);

        let domain_sep = domain_separator(&contract_addr);
        let digest = order.eip712_digest(&domain_sep);
        let sig = sign_digest(&sk, &digest).unwrap();

        assert_eq!(sig.len(), 65);
        assert!(sig[64] == 27 || sig[64] == 28);

        // Verify we can recover the correct signer
        let recovery_id = RecoveryId::try_from(sig[64] - 27).unwrap();
        let signature = Signature::from_bytes((&sig[..64]).into()).unwrap();
        let recovered_vk =
            VerifyingKey::recover_from_prehash(&digest, &signature, recovery_id).unwrap();

        // Derive address from recovered verifying key
        let recovered_pubkey = recovered_vk.to_encoded_point(false);
        let recovered_hash = keccak256(&recovered_pubkey.as_bytes()[1..]);
        let mut recovered_addr = [0u8; 20];
        recovered_addr.copy_from_slice(&recovered_hash[12..32]);

        // The recovered address must match the maker address
        assert_eq!(recovered_addr, addr);
    }
}
