//! Limitless prediction market exchange client (Base chain).
//!
//! Implements the Exchange trait for Limitless, a prediction market on Base (chain ID 8453).
//! Limitless is a fork of Polymarket's CTF Exchange with per-market exchange addresses.
//! Uses API key authentication (X-API-Key header) 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 k256::ecdsa::{RecoveryId, Signature, SigningKey, VerifyingKey};
use sha3::{Digest, Keccak256};
use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::{Arc, Mutex};
use tracing::{debug, error, info, warn};
use zeroize::Zeroizing;

const DEFAULT_API_URL: &str = "https://api.limitless.exchange";

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

/// EIP-712 domain separator components for Limitless CTF Exchange on Base.
const DOMAIN_NAME: &str = "Limitless CTF Exchange";
const DOMAIN_VERSION: &str = "1";
const CHAIN_ID: u64 = 8453;

/// Validate that a URL path parameter contains only safe characters.
fn validate_url_param(param: &str, name: &str) -> Result<(), HorizonError> {
    if param.is_empty() {
        return Err(HorizonError::Exchange(format!(
            "limitless: {} is empty",
            name
        )));
    }
    if !param
        .bytes()
        .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_' || b == b'.')
    {
        return Err(HorizonError::Exchange(format!(
            "limitless: invalid characters in {}",
            name
        )));
    }
    Ok(())
}

// -- 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 for a given contract address.
///
///   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 Limitless CTF Exchange order for EIP-712 signing.
/// Same 12-field Order struct as Polymarket (Limitless is a CTF Exchange fork).
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 x 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!("limitless: 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)
}

/// 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!("limitless: invalid private key hex: {}", e))
    })?;
    SigningKey::from_bytes((&bytes[..]).into()).map_err(|e| {
        HorizonError::Exchange(format!("limitless: invalid private key: {}", e))
    })
}

// -- LimitlessExchange --------------------------------------------------------

pub struct LimitlessExchange {
    /// API key for X-API-Key header authentication (lmts_...).
    api_key: Zeroizing<String>,
    /// Owner ID included in order requests.
    owner_id: Zeroizing<String>,
    api_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).
    /// SAFETY: k256's SigningKey internally wraps NonZeroScalar which implements
    /// zeroize::Zeroize via the zeroize feature, so the key material is securely
    /// erased on drop. No manual Drop implementation is needed.
    signing_key: Option<SigningKey>,
    /// Ethereum address derived from the signing key (20 bytes).
    maker_address: [u8; 20],
    /// Tracks open order IDs per market (slug) 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>>,
    /// Per-market exchange contract addresses: slug -> 20-byte address.
    /// Each market on Limitless may have its own exchange contract.
    exchange_addresses: Mutex<HashMap<String, [u8; 20]>>,
    /// Cached domain separators per exchange address (avoids 5 keccak256 per order).
    domain_sep_cache: Mutex<HashMap<[u8; 20], [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>>,
}

impl LimitlessExchange {
    pub fn new(
        api_key: String,
        owner_id: String,
        private_key: Option<String>,
        api_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)),
                        "limitless: loaded signing key"
                    );
                    (Some(sk), addr)
                }
                Err(e) => {
                    warn!(error = %e, "limitless: invalid private key, order signing disabled");
                    (None, [0u8; 20])
                }
            },
            _ => {
                debug!("limitless: no private key provided, order signing disabled");
                (None, [0u8; 20])
            }
        };

        Self {
            api_key: Zeroizing::new(api_key),
            owner_id: Zeroizing::new(owner_id),
            api_url: api_url.unwrap_or_else(|| DEFAULT_API_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()),
            exchange_addresses: Mutex::new(HashMap::new()),
            domain_sep_cache: Mutex::new(HashMap::new()),
            seen_fill_ids: Mutex::new(HashSet::new()),
            seen_fill_order: Mutex::new(VecDeque::new()),
        }
    }

    /// Register an exchange contract address for a specific market slug.
    ///
    /// Limitless uses per-market exchange addresses (from `market.venue.exchange`).
    /// The domain separator is cached for each unique address.
    pub fn register_exchange_address(
        &self,
        slug: &str,
        address_hex: &str,
    ) -> Result<(), HorizonError> {
        let clean = address_hex.strip_prefix("0x").unwrap_or(address_hex);
        let bytes = hex::decode(clean).map_err(|e| {
            HorizonError::Exchange(format!(
                "limitless: invalid exchange address hex '{}': {}",
                address_hex, e
            ))
        })?;
        if bytes.len() != 20 {
            return Err(HorizonError::Exchange(format!(
                "limitless: exchange address must be 20 bytes, got {}",
                bytes.len()
            )));
        }
        let mut addr = [0u8; 20];
        addr.copy_from_slice(&bytes);

        // Cache the domain separator for this address
        {
            let mut cache = self
                .domain_sep_cache
                .lock()
                .unwrap_or_else(|e| e.into_inner());
            if !cache.contains_key(&addr) {
                let ds = domain_separator(&addr);
                cache.insert(addr, ds);
                debug!(
                    address = %format!("0x{}", hex::encode(addr)),
                    "limitless: cached domain separator for exchange address"
                );
            }
        }

        // Register slug -> address mapping
        {
            let mut addrs = self
                .exchange_addresses
                .lock()
                .unwrap_or_else(|e| e.into_inner());
            addrs.insert(slug.to_string(), addr);
        }

        info!(
            slug = %slug,
            address = %format!("0x{}", hex::encode(addr)),
            "limitless: registered exchange address"
        );
        Ok(())
    }

    /// Add API key auth header to a request builder.
    fn auth_headers(
        &self,
        builder: reqwest::RequestBuilder,
    ) -> reqwest::RequestBuilder {
        builder
            .header("X-API-Key", &**self.api_key)
            .header("Content-Type", "application/json")
    }

    /// Convert OrderSide to Limitless API string.
    fn 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 side_u8(order_side: OrderSide) -> u8 {
        match order_side {
            OrderSide::Buy => 0,
            OrderSide::Sell => 1,
        }
    }

    /// Map TimeInForce to Limitless string.
    fn tif_str(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 order type based on OrderType and TimeInForce.
    fn order_type_str(req: &OrderRequest) -> &'static str {
        match req.order_type {
            crate::types::OrderType::Market => "FOK",
            crate::types::OrderType::Limit => Self::tif_str(req.time_in_force),
        }
    }

    /// Look up the domain separator for a market slug.
    /// Returns an error if no exchange address has been registered for this slug.
    fn get_domain_sep(&self, slug: &str) -> Result<[u8; 32], HorizonError> {
        let addr = {
            let addrs = self
                .exchange_addresses
                .lock()
                .unwrap_or_else(|e| e.into_inner());
            addrs.get(slug).copied().ok_or_else(|| {
                HorizonError::Exchange(format!(
                    "limitless: no exchange address registered for slug '{}'. \
                     Call register_limitless_exchange_address() first.",
                    slug
                ))
            })?
        };

        let cache = self
            .domain_sep_cache
            .lock()
            .unwrap_or_else(|e| e.into_inner());
        cache.get(&addr).copied().ok_or_else(|| {
            HorizonError::Exchange(format!(
                "limitless: domain separator not cached for address 0x{}",
                hex::encode(addr)
            ))
        })
    }

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

        if !req.price.is_finite() || req.price <= 0.0 {
            return Err(HorizonError::Exchange(format!(
                "limitless: invalid price: {}",
                req.price
            )));
        }
        if !req.size.is_finite() || req.size <= 0.0 {
            return Err(HorizonError::Exchange(format!(
                "limitless: invalid size: {}",
                req.size
            )));
        }

        // Look up domain separator for this market
        let domain_sep = self.get_domain_sep(&req.market_id)?;

        // Generate a cryptographically random salt from UUID v4
        let salt: u128 = u128::from_be_bytes(*uuid::Uuid::new_v4().as_bytes());

        // 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::side_u8(req.order_side),
            signature_type: 0, // EOA
        };

        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 Limitless 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::side_str(req.order_side),
                "signatureType": 0,
                "signature": sig_hex,
            },
            "ownerId": &**self.owner_id,
            "orderType": Self::order_type_str(req),
        });

        Ok(body)
    }

    /// Submit order to Limitless with exponential backoff on 429.
    async fn submit_order_async(&self, req: &OrderRequest) -> Result<String, HorizonError> {
        let body = self.build_signed_order(req)?;
        let url = format!("{}/orders", self.api_url);

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

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

        for attempt in 0..=MAX_RETRIES {
            let builder = self.client.post(&url).json(&body);
            let resp = self
                .auth_headers(builder)
                .send()
                .await
                .map_err(|e| {
                    HorizonError::Exchange(format!("limitless request failed: {}", e))
                })?;

            let status_code = resp.status().as_u16();

            if status_code == 429 && attempt < MAX_RETRIES {
                warn!(
                    attempt = attempt + 1,
                    backoff_ms = backoff_ms,
                    "limitless: 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 status_code == 401 || status_code == 403 {
                let body_text = resp.text().await.unwrap_or_default();
                return Err(HorizonError::Exchange(format!(
                    "limitless: forbidden ({}) -- check API key: {}",
                    status_code, body_text
                )));
            }

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

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

            let order_id = json
                .get("orderId")
                .or_else(|| 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!(
                        "limitless: response missing order ID: {}",
                        json
                    ))
                })?;

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

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

    /// Cancel order on Limitless.
    async fn cancel_order_async(&self, order_id: &str) -> Result<(), HorizonError> {
        validate_url_param(order_id, "order_id")?;
        let url = format!("{}/orders/{}", self.api_url, order_id);

        let builder = self.client.delete(&url);
        let resp = self
            .auth_headers(builder)
            .send()
            .await
            .map_err(|e| {
                HorizonError::Exchange(format!("limitless cancel failed: {}", e))
            })?;

        if resp.status().as_u16() == 404 {
            // Order already filled or canceled
            debug!(order_id = %order_id, "limitless: order not found (may be filled)");
            return Ok(());
        }

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

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

    /// Cancel all orders for a specific market slug on Limitless.
    async fn cancel_all_for_slug_async(&self, slug: &str) -> Result<usize, HorizonError> {
        validate_url_param(slug, "slug")?;
        let url = format!("{}/orders/all/{}", self.api_url, slug);

        let builder = self.client.delete(&url);
        let resp = self
            .auth_headers(builder)
            .send()
            .await
            .map_err(|e| {
                HorizonError::Exchange(format!("limitless 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!(
                "limitless cancel-all failed ({}): {}",
                status, body
            )));
        }

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

        info!(slug = %slug, count = count, "limitless: canceled all orders for slug");
        Ok(count as usize)
    }

    /// Cancel all orders across all tracked markets.
    async fn cancel_all_async(&self) -> Result<usize, HorizonError> {
        // Collect all tracked market slugs
        let slugs: Vec<String> = {
            let map = self
                .open_orders
                .lock()
                .unwrap_or_else(|e| e.into_inner());
            map.keys().cloned().collect()
        };

        let mut total_canceled = 0;
        for slug in &slugs {
            match self.cancel_all_for_slug_async(slug).await {
                Ok(count) => total_canceled += count,
                Err(e) => {
                    debug!(
                        slug = %slug,
                        error = %e,
                        "limitless: cancel-all for slug failed"
                    );
                }
            }
        }

        info!(count = total_canceled, "limitless: canceled all orders");
        Ok(total_canceled)
    }

    /// Poll for recent trades/fills from Limitless.
    async fn poll_fills_async(&self) -> Result<Vec<Fill>, HorizonError> {
        let url = format!("{}/portfolio/trades", self.api_url);

        let builder = self.client.get(&url);
        let resp = self
            .auth_headers(builder)
            .send()
            .await
            .map_err(|e| {
                HorizonError::Exchange(format!("limitless: fill poll failed: {}", e))
            })?;

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

        let json: serde_json::Value = resp.json().await.map_err(|e| {
            HorizonError::Exchange(format!("limitless: 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("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")
                    .or_else(|| trade.get("orderID"))
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string();

                let market_id = trade
                    .get("slug")
                    .or_else(|| 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);

                // Validate: skip entries with non-positive or NaN/Inf filled quantity
                if size <= 0.0 || !size.is_finite() {
                    continue;
                }
                if !price.is_finite() || price <= 0.0 {
                    continue;
                }

                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!("limitless_fill_{}", *next_id);
                    *next_id += 1;
                    id
                } else {
                    trade_id.to_string()
                };

                let is_maker = trade
                    .get("isMaker")
                    .or_else(|| trade.get("is_maker"))
                    .and_then(|v| v.as_bool())
                    .unwrap_or(false);

                // 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")
                        .or_else(|| trade.get("tokenID"))
                        .and_then(|v| v.as_str())
                        .map(|s| s.to_string()),
                    exchange: "limitless".to_string(),
                    is_maker,
                });
            }
        }

        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(), "limitless: polled new fills");
        }

        Ok(new_fills)
    }

    /// Fetch positions from Limitless for reconciliation.
    async fn fetch_positions_async(&self) -> Result<Vec<crate::types::Position>, HorizonError> {
        let url = format!("{}/portfolio/positions", self.api_url);

        let builder = self.client.get(&url);
        let resp = self
            .auth_headers(builder)
            .send()
            .await
            .map_err(|e| {
                HorizonError::Exchange(format!("limitless: position fetch failed: {}", e))
            })?;

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

        let json: serde_json::Value = resp.json().await.map_err(|e| {
            HorizonError::Exchange(format!("limitless: 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 slug = p
                    .get("slug")
                    .or_else(|| 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 && size.is_finite() {
                    // 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 {
                        if !outcome_str.is_empty()
                            && !outcome_str.eq_ignore_ascii_case("yes")
                        {
                            warn!(
                                outcome = %outcome_str,
                                "limitless: unknown position outcome, defaulting to YES"
                            );
                        }
                        Side::Yes
                    };

                    let token_id = p
                        .get("tokenId")
                        .or_else(|| p.get("asset_id"))
                        .and_then(|v| v.as_str())
                        .map(|s| s.to_string());

                    positions.push(crate::types::Position {
                        market_id: slug.to_string(),
                        side,
                        size,
                        avg_entry_price: avg_price,
                        realized_pnl: 0.0,
                        unrealized_pnl: 0.0,
                        token_id,
                        exchange: "limitless".to_string(),
                    });
                }
            }
        }

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

        Ok(positions)
    }
}

impl Exchange for LimitlessExchange {
    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());
            // Evict empty market entries to bound growth
            map.retain(|_, ids| !ids.is_empty());
            // Hard cap: if tracking > 10k order IDs total, clear stale entries
            let total: usize = map.values().map(|v| v.len()).sum();
            if total > 10_000 {
                warn!("limitless: open_orders tracking exceeds 10k, clearing stale entries");
                map.clear();
                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);
            // Hard cap: evict oldest entries if > 10k
            if map.len() > 10_000 {
                warn!("limitless: order_sides tracking exceeds 10k, clearing stale entries");
                map.clear();
                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> {
        // Try the bulk cancel endpoint first
        match self
            .runtime
            .block_on(self.cancel_all_for_slug_async(market_id))
        {
            Ok(count) => {
                // Clear tracked orders for this market
                if let Ok(mut map) = self.open_orders.lock() {
                    if let Some(ids) = map.remove(market_id) {
                        // Clean up order_sides for canceled orders
                        if let Ok(mut sides) = self.order_sides.lock() {
                            for oid in &ids {
                                sides.remove(oid);
                            }
                        }
                    }
                }
                Ok(count)
            }
            Err(_) => {
                // Fallback: cancel individually from tracked orders
                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,
                                "limitless: 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(),
                    "limitless: 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, "limitless: fill polling error");
            }
        }

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

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

    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(8453);
        // 8453 = 0x2105
        assert_eq!(result[30], 0x21);
        assert_eq!(result[31], 0x05);
        assert!(result[..30].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 addr_bytes = hex::decode("4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E").unwrap();
        let mut addr = [0u8; 20];
        addr.copy_from_slice(&addr_bytes);

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

    #[test]
    fn test_domain_separator_differs_from_polymarket() {
        // Limitless uses different domain name and chain ID than Polymarket,
        // so the domain separator must be different even for the same contract address.
        let addr_bytes = hex::decode("4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E").unwrap();
        let mut addr = [0u8; 20];
        addr.copy_from_slice(&addr_bytes);

        let limitless_ds = domain_separator(&addr);
        // This is the Limitless domain separator, it should not be all zeros
        assert!(limitless_ds.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_validate_url_param_valid() {
        assert!(validate_url_param("abc-123_def", "test").is_ok());
    }

    #[test]
    fn test_validate_url_param_empty() {
        assert!(validate_url_param("", "test").is_err());
    }

    #[test]
    fn test_validate_url_param_special_chars() {
        assert!(validate_url_param("abc/../etc", "test").is_err());
        assert!(validate_url_param("abc def", "test").is_err());
    }

    #[test]
    fn test_validate_url_param_slash() {
        assert!(validate_url_param("abc/def", "test").is_err());
    }

    #[test]
    fn test_validate_url_param_uuid() {
        assert!(validate_url_param("550e8400-e29b-41d4-a716-446655440000", "test").is_ok());
    }

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

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

    #[test]
    fn test_parse_private_key_invalid() {
        assert!(parse_private_key("not-hex").is_err());
        assert!(parse_private_key("0xZZZZ").is_err());
    }

    #[test]
    fn test_side_str() {
        assert_eq!(LimitlessExchange::side_str(OrderSide::Buy), "BUY");
        assert_eq!(LimitlessExchange::side_str(OrderSide::Sell), "SELL");
    }

    #[test]
    fn test_side_u8() {
        assert_eq!(LimitlessExchange::side_u8(OrderSide::Buy), 0);
        assert_eq!(LimitlessExchange::side_u8(OrderSide::Sell), 1);
    }
}
