//! Alpaca Markets REST exchange client.
//!
//! Implements the Exchange trait for Alpaca's Trading API.
//! Supports both paper and live trading via header-based auth.
//! Includes fill polling and order management.

use crate::error::HorizonError;
use crate::exchanges::Exchange;
use crate::types::{Fill, OrderRequest, OrderSide, Position, Side};
use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::{Arc, Mutex};
use tracing::{debug, error, info, warn};
use zeroize::Zeroizing;

const LIVE_API_URL: &str = "https://api.alpaca.markets";
const PAPER_API_URL: &str = "https://paper-api.alpaca.markets";

/// 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!(
            "alpaca: {} is empty",
            name
        )));
    }
    if !param
        .bytes()
        .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
    {
        return Err(HorizonError::Exchange(format!(
            "alpaca: invalid characters in {}",
            name
        )));
    }
    Ok(())
}

pub struct AlpacaExchange {
    api_url: String,
    api_key: Zeroizing<String>,
    api_secret: Zeroizing<String>,
    client: reqwest::Client,
    runtime: Arc<tokio::runtime::Runtime>,
    fills: Mutex<Vec<Fill>>,
    next_fill_id: Mutex<u64>,
    last_fill_cursor: Mutex<Option<String>>,
    /// Tracks open order IDs per market (symbol) for per-market cancellation.
    open_orders: Mutex<HashMap<String, Vec<String>>>,
    /// Seen fill IDs for deduplication.
    seen_fill_ids: Mutex<HashSet<String>>,
    /// FIFO order for seen fill IDs (oldest first for eviction).
    seen_fill_order: Mutex<VecDeque<String>>,
    is_paper: bool,
}

impl AlpacaExchange {
    pub fn new(
        api_key: String,
        api_secret: String,
        api_url: Option<String>,
        is_paper: bool,
        runtime: Arc<tokio::runtime::Runtime>,
    ) -> Self {
        let default_url = if is_paper {
            PAPER_API_URL.to_string()
        } else {
            LIVE_API_URL.to_string()
        };
        Self {
            api_url: api_url.unwrap_or(default_url),
            api_key: Zeroizing::new(api_key),
            api_secret: Zeroizing::new(api_secret),
            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_cursor: Mutex::new(None),
            open_orders: Mutex::new(HashMap::new()),
            seen_fill_ids: Mutex::new(HashSet::new()),
            seen_fill_order: Mutex::new(VecDeque::new()),
            is_paper,
        }
    }

    /// Add Alpaca auth headers to a request builder.
    fn auth_headers(
        &self,
        builder: reqwest::RequestBuilder,
    ) -> reqwest::RequestBuilder {
        builder
            .header("APCA-API-KEY-ID", &**self.api_key)
            .header("APCA-API-SECRET-KEY", &**self.api_secret)
    }

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

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

        let side_str = match req.order_side {
            OrderSide::Buy => "buy",
            OrderSide::Sell => "sell",
        };

        let body = serde_json::json!({
            "symbol": req.market_id,
            "qty": format!("{}", req.size),
            "side": side_str,
            "type": "limit",
            "time_in_force": "gtc",
            "limit_price": format!("{:.2}", req.price),
        });

        debug!(
            symbol = %req.market_id,
            side = side_str,
            price = req.price,
            size = req.size,
            "alpaca: submitting order"
        );

        let mut backoff_ms: u64 = 1000;

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

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

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

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

            let order_id = 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!(
                        "alpaca: response missing order id: {}",
                        json
                    ))
                })?;

            info!(order_id = %order_id, "alpaca: order accepted");
            return Ok(order_id);
        }

        Err(HorizonError::Exchange(
            "alpaca: retry attempts exhausted".into(),
        ))
    }

    /// Cancel order on Alpaca.
    async fn cancel_order_async(&self, order_id: &str) -> Result<(), HorizonError> {
        validate_url_param(order_id, "order_id")?;
        let url = format!("{}/v2/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!("alpaca cancel failed: {}", e)))?;

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

        if !resp.status().is_success() && resp.status().as_u16() != 204 {
            let status = resp.status();
            let body = resp.text().await.unwrap_or_default();
            return Err(HorizonError::Exchange(format!(
                "alpaca cancel failed ({}): {}",
                status, body
            )));
        }

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

    /// Cancel all orders on Alpaca.
    async fn cancel_all_async(&self) -> Result<usize, HorizonError> {
        let url = format!("{}/v2/orders", self.api_url);

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

        // Alpaca returns an array of canceled orders (207) or empty on success
        let json: serde_json::Value = resp.json().await.unwrap_or_else(|_| serde_json::Value::Null);
        let count = json.as_array().map(|a| a.len()).unwrap_or(0);

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

    /// Poll for filled orders from Alpaca.
    async fn poll_fills_async(&self) -> Result<Vec<Fill>, HorizonError> {
        let mut url = format!("{}/v2/orders?status=filled&limit=100&direction=desc", self.api_url);

        // Add after cursor for incremental polling
        if let Ok(cursor) = self.last_fill_cursor.lock() {
            if let Some(ref c) = *cursor {
                url = format!("{}&after={}", url, c);
            }
        }

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

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

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

        let mut new_fills = Vec::new();

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

            for order_json in orders_arr {
                let order_id = order_json
                    .get("id")
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string();

                let symbol = order_json
                    .get("symbol")
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string();

                let filled_qty = order_json
                    .get("filled_qty")
                    .and_then(|v| v.as_str().and_then(|s| s.parse::<f64>().ok()).or_else(|| v.as_f64()))
                    .unwrap_or(0.0);

                let filled_avg_price = order_json
                    .get("filled_avg_price")
                    .and_then(|v| v.as_str().and_then(|s| s.parse::<f64>().ok()).or_else(|| v.as_f64()))
                    .unwrap_or(0.0);

                let side_str = order_json
                    .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 ts = order_json
                    .get("filled_at")
                    .or_else(|| order_json.get("updated_at"))
                    .and_then(|v| v.as_str())
                    .and_then(|s| s.parse::<f64>().ok())
                    .unwrap_or(0.0);

                if filled_qty <= 0.0 {
                    continue;
                }

                let fill_id = if order_id.is_empty() {
                    let id = format!("alpaca_fill_{}", *next_id);
                    *next_id += 1;
                    id
                } else {
                    order_id.clone()
                };

                // Dedup by fill ID
                {
                    let mut seen = self
                        .seen_fill_ids
                        .lock()
                        .unwrap_or_else(|e| e.into_inner());
                    if seen.contains(&fill_id) {
                        continue;
                    }
                    seen.insert(fill_id.clone());
                    if let Ok(mut order) = self.seen_fill_order.lock() {
                        order.push_back(fill_id.clone());
                        while seen.len() > 10_000 {
                            if let Some(oldest) = order.pop_front() {
                                seen.remove(&oldest);
                            } else {
                                break;
                            }
                        }
                    }
                }

                // Update cursor to latest order ID
                if let Ok(mut cursor) = self.last_fill_cursor.lock() {
                    *cursor = Some(order_id.clone());
                }

                new_fills.push(Fill {
                    fill_id,
                    order_id,
                    market_id: symbol,
                    side: Side::Yes, // Stocks don't have Yes/No sides
                    order_side,
                    price: filled_avg_price,
                    size: filled_qty,
                    fee: 0.0, // Alpaca is commission-free for stocks
                    timestamp: ts,
                    token_id: None,
                    exchange: "alpaca".to_string(),
                    is_maker: false,
                });
            }
        }

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

        Ok(new_fills)
    }

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

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

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

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

        let mut positions = Vec::new();

        if let Some(arr) = json.as_array() {
            for p in arr {
                let symbol = p
                    .get("symbol")
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string();
                let qty = p
                    .get("qty")
                    .and_then(|v| {
                        v.as_str()
                            .and_then(|s| s.parse::<f64>().ok())
                            .or_else(|| v.as_f64())
                    })
                    .unwrap_or(0.0);
                let avg_entry = p
                    .get("avg_entry_price")
                    .and_then(|v| {
                        v.as_str()
                            .and_then(|s| s.parse::<f64>().ok())
                            .or_else(|| v.as_f64())
                    })
                    .unwrap_or(0.0);
                let unrealized_pl = p
                    .get("unrealized_pl")
                    .and_then(|v| {
                        v.as_str()
                            .and_then(|s| s.parse::<f64>().ok())
                            .or_else(|| v.as_f64())
                    })
                    .unwrap_or(0.0);

                if qty.abs() > 0.0 {
                    positions.push(Position {
                        market_id: symbol,
                        side: Side::Yes,
                        size: qty,
                        avg_entry_price: avg_entry,
                        realized_pnl: 0.0,
                        unrealized_pnl: unrealized_pl,
                        token_id: None,
                        exchange: "alpaca".to_string(),
                    });
                }
            }
        }

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

        Ok(positions)
    }
}

impl Exchange for AlpacaExchange {
    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());
        }

        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);
            }
        }

        Ok(())
    }

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

        if let Ok(mut map) = self.open_orders.lock() {
            map.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,
                        "alpaca: 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(),
            "alpaca: canceled orders for market"
        );
        Ok(canceled)
    }

    fn drain_fills(&self) -> Vec<Fill> {
        // Poll for new fills from the API
        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());
                    }

                    if let Ok(mut fills) = self.fills.lock() {
                        fills.extend(new_fills);
                    }
                }
            }
            Err(e) => {
                warn!(error = %e, "alpaca: fill polling error");
            }
        }

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

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

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

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

    #[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());
    }
}
