use std::collections::HashMap;

use bpx_api_types::fill::Fill;
use bpx_api_types::order::Side;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use serde::Serialize;
use tabled::settings::Style;
use tabled::{Table, Tabled};

use crate::output::OutputFormat;

/// Format a PnL decimal for table display (no ANSI colors).
fn format_pnl_value(val: Decimal) -> String {
    if val < Decimal::ZERO {
        format!("-${:.2}", val.abs())
    } else if val > Decimal::ZERO {
        format!("+${:.2}", val)
    } else {
        format!("${:.2}", val)
    }
}

#[derive(Tabled)]
struct FillRow {
    #[tabled(rename = "TRADE ID")]
    trade_id: String,
    #[tabled(rename = "ORDER ID")]
    order_id: String,
    #[tabled(rename = "SYMBOL")]
    symbol: String,
    #[tabled(rename = "SIDE")]
    side: String,
    #[tabled(rename = "PRICE")]
    price: String,
    #[tabled(rename = "QTY")]
    quantity: String,
    #[tabled(rename = "FEE")]
    fee: String,
    #[tabled(rename = "MAKER")]
    is_maker: String,
    #[tabled(rename = "TIME")]
    timestamp: String,
}

pub fn print_fills(fills: &[Fill], format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(fills),
        OutputFormat::Table => {
            if fills.is_empty() {
                println!("No fills found.");
                return;
            }
            let rows: Vec<FillRow> = fills
                .iter()
                .map(|f| {
                    let side_display = if f.side == Side::Bid {
                        "BUY".to_string()
                    } else {
                        "SELL".to_string()
                    };
                    FillRow {
                        trade_id: f
                            .trade_id
                            .map(|id| id.to_string())
                            .unwrap_or_else(|| "-".to_string()),
                        order_id: f.order_id.clone(),
                        symbol: f.symbol.clone(),
                        side: side_display,
                        price: f.price.to_string(),
                        quantity: f.quantity.to_string(),
                        fee: f.fee.to_string(),
                        is_maker: if f.is_maker {
                            "Yes".to_string()
                        } else {
                            "No".to_string()
                        },
                        timestamp: f
                            .timestamp
                            .parse::<i64>()
                            .map(super::format_ts_ms)
                            .unwrap_or_else(|_| f.timestamp.clone()),
                    }
                })
                .collect();
            let mut table = Table::new(rows);
            table.with(Style::blank());
            println!("{table}");
        }
    }
}

// ---- Analytics ----

#[derive(Debug, Serialize)]
struct SymbolAnalytics {
    symbol: String,
    trades: usize,
    win_pct: f64,
    vwap_buy: Decimal,
    vwap_sell: Decimal,
    realized_pnl: Decimal,
    total_fees: Decimal,
    net_pnl: Decimal,
}

#[derive(Debug, Serialize)]
struct PortfolioAnalytics {
    symbols: Vec<SymbolAnalytics>,
    total_trades: usize,
    total_win_pct: f64,
    total_pnl: Decimal,
    total_fees: Decimal,
    total_net_pnl: Decimal,
    sharpe_ratio: f64,
    profit_factor: f64,
}

#[derive(Tabled)]
struct AnalyticsRow {
    #[tabled(rename = "SYMBOL")]
    symbol: String,
    #[tabled(rename = "TRADES")]
    trades: String,
    #[tabled(rename = "WIN%")]
    win_pct: String,
    #[tabled(rename = "PnL")]
    pnl: String,
    #[tabled(rename = "FEES")]
    fees: String,
    #[tabled(rename = "NET PnL")]
    net_pnl: String,
}

fn compute_analytics(fills: &[Fill]) -> PortfolioAnalytics {
    let mut by_symbol: HashMap<String, Vec<&Fill>> = HashMap::new();
    for fill in fills {
        by_symbol.entry(fill.symbol.clone()).or_default().push(fill);
    }

    let mut symbols = Vec::new();
    let mut portfolio_gross_profit = Decimal::ZERO;
    let mut portfolio_gross_loss = Decimal::ZERO;
    let mut all_daily_pnls: HashMap<String, Decimal> = HashMap::new();

    for (symbol, sym_fills) in &by_symbol {
        let mut buy_cost = Decimal::ZERO;
        let mut buy_vol = Decimal::ZERO;
        let mut sell_cost = Decimal::ZERO;
        let mut sell_vol = Decimal::ZERO;
        let mut fees = Decimal::ZERO;

        for fill in sym_fills {
            let cost = fill.price * fill.quantity;
            fees += fill.fee;

            if fill.side == Side::Bid {
                buy_cost += cost;
                buy_vol += fill.quantity;
            } else {
                sell_cost += cost;
                sell_vol += fill.quantity;
            }

            // Group PnL by day for Sharpe calculation
            if let Ok(ts) = fill.timestamp.parse::<i64>() {
                let day_key = format!("{}_{}", symbol, ts / (24 * 60 * 60 * 1000));
                let day_entry = all_daily_pnls.entry(day_key).or_default();
                if fill.side == Side::Ask {
                    *day_entry += cost;
                } else {
                    *day_entry -= cost;
                }
            }
        }

        let vwap_buy = if buy_vol > Decimal::ZERO {
            buy_cost / buy_vol
        } else {
            Decimal::ZERO
        };
        let vwap_sell = if sell_vol > Decimal::ZERO {
            sell_cost / sell_vol
        } else {
            Decimal::ZERO
        };

        let matched_qty = buy_vol.min(sell_vol);
        let realized_pnl =
            if matched_qty > Decimal::ZERO && vwap_sell > Decimal::ZERO && vwap_buy > Decimal::ZERO
            {
                matched_qty * (vwap_sell - vwap_buy)
            } else {
                Decimal::ZERO
            };

        // Win rate: approximate from per-symbol avg buy vs avg sell
        let win_pct = if matched_qty > Decimal::ZERO && vwap_sell > vwap_buy {
            100.0
        } else {
            0.0
        };

        if realized_pnl > Decimal::ZERO {
            portfolio_gross_profit += realized_pnl;
        } else {
            portfolio_gross_loss += realized_pnl.abs();
        }

        let net = realized_pnl - fees;

        symbols.push(SymbolAnalytics {
            symbol: symbol.clone(),
            trades: sym_fills.len(),
            win_pct,
            vwap_buy,
            vwap_sell,
            realized_pnl,
            total_fees: fees,
            net_pnl: net,
        });
    }

    symbols.sort_by(|a, b| b.net_pnl.cmp(&a.net_pnl));

    let total_trades: usize = symbols.iter().map(|s| s.trades).sum();
    let total_pnl: Decimal = symbols.iter().map(|s| s.realized_pnl).sum();
    let total_fees: Decimal = symbols.iter().map(|s| s.total_fees).sum();
    let total_net_pnl = total_pnl - total_fees;

    // Portfolio win rate: % of symbols with positive PnL
    let winning_symbols = symbols
        .iter()
        .filter(|s| s.realized_pnl > Decimal::ZERO)
        .count();
    let total_with_trades = symbols
        .iter()
        .filter(|s| s.vwap_buy > Decimal::ZERO && s.vwap_sell > Decimal::ZERO)
        .count();
    let total_win_pct = if total_with_trades > 0 {
        winning_symbols as f64 / total_with_trades as f64 * 100.0
    } else {
        0.0
    };

    // Sharpe ratio: mean(daily_pnl) / stdev(daily_pnl) * sqrt(365)
    let daily_pnls: Vec<f64> = all_daily_pnls.values().filter_map(|d| d.to_f64()).collect();
    let sharpe_ratio = if daily_pnls.len() > 1 {
        let mean = daily_pnls.iter().sum::<f64>() / daily_pnls.len() as f64;
        let variance = daily_pnls.iter().map(|x| (x - mean).powi(2)).sum::<f64>()
            / (daily_pnls.len() - 1) as f64;
        let stdev = variance.sqrt();
        if stdev > 0.0 {
            (mean / stdev) * (365.0_f64).sqrt()
        } else {
            0.0
        }
    } else {
        0.0
    };

    // Profit factor: gross_profit / gross_loss
    let profit_factor = if portfolio_gross_loss > Decimal::ZERO {
        portfolio_gross_profit.to_f64().unwrap_or(0.0)
            / portfolio_gross_loss.to_f64().unwrap_or(1.0)
    } else if portfolio_gross_profit > Decimal::ZERO {
        f64::INFINITY
    } else {
        0.0
    };

    PortfolioAnalytics {
        symbols,
        total_trades,
        total_win_pct,
        total_pnl,
        total_fees,
        total_net_pnl,
        sharpe_ratio,
        profit_factor,
    }
}

pub fn print_analytics(fills: &[Fill], format: OutputFormat) {
    if fills.is_empty() {
        match format {
            OutputFormat::Json => super::print_json(&serde_json::json!({"analytics": {}})),
            OutputFormat::Table => println!("No fills found."),
        }
        return;
    }

    let analytics = compute_analytics(fills);

    match format {
        OutputFormat::Json => {
            super::print_json(&analytics);
        }
        OutputFormat::Table => {
            let mut rows: Vec<AnalyticsRow> = analytics
                .symbols
                .iter()
                .map(|sym| {
                    let pnl_display = format_pnl_value(sym.realized_pnl);
                    let net_display = format_pnl_value(sym.net_pnl);

                    AnalyticsRow {
                        symbol: sym.symbol.clone(),
                        trades: sym.trades.to_string(),
                        win_pct: format!("{:.0}%", sym.win_pct),
                        pnl: pnl_display,
                        fees: format!("${:.2}", sym.total_fees),
                        net_pnl: net_display,
                    }
                })
                .collect();

            // Portfolio totals row
            rows.push(AnalyticsRow {
                symbol: "PORTFOLIO".to_string(),
                trades: analytics.total_trades.to_string(),
                win_pct: format!("{:.0}%", analytics.total_win_pct),
                pnl: format_pnl_value(analytics.total_pnl),
                fees: format!("${:.2}", analytics.total_fees),
                net_pnl: format_pnl_value(analytics.total_net_pnl),
            });

            println!();
            println!(
                "  {} fills across {} symbols",
                fills.len(),
                analytics.symbols.len()
            );
            println!();

            let mut table = Table::new(rows);
            table.with(Style::blank());
            println!("{table}");

            // Footer metrics
            let sharpe_str = if analytics.sharpe_ratio.is_finite() {
                format!("{:.2}", analytics.sharpe_ratio)
            } else {
                "N/A".to_string()
            };
            let pf_str = if analytics.profit_factor.is_finite() {
                format!("{:.1}", analytics.profit_factor)
            } else {
                "INF".to_string()
            };

            println!();
            println!(
                "  FEES: ${:.2}  SHARPE: {}  PROFIT FACTOR: {}",
                analytics.total_fees, sharpe_str, pf_str
            );
            println!();
        }
    }
}

// ---- Summary ----

#[derive(Debug, Default, Serialize)]
struct SymbolSummary {
    symbol: String,
    total_trades: usize,
    buy_count: usize,
    sell_count: usize,
    buy_volume: Decimal,
    sell_volume: Decimal,
    total_volume: Decimal,
    total_fees: Decimal,
    avg_buy_price: Decimal,
    avg_sell_price: Decimal,
    realized_pnl: Decimal,
}

#[derive(Tabled)]
struct SummaryRow {
    #[tabled(rename = "SYMBOL")]
    symbol: String,
    #[tabled(rename = "TRADES")]
    trades: String,
    #[tabled(rename = "BUYS")]
    buys: String,
    #[tabled(rename = "SELLS")]
    sells: String,
    #[tabled(rename = "VOLUME")]
    volume: String,
    #[tabled(rename = "FEES")]
    fees: String,
    #[tabled(rename = "AVG BUY")]
    avg_buy: String,
    #[tabled(rename = "AVG SELL")]
    avg_sell: String,
    #[tabled(rename = "PnL")]
    pnl: String,
}

fn compute_summary(fills: &[Fill]) -> Vec<SymbolSummary> {
    let mut by_symbol: HashMap<String, SymbolSummary> = HashMap::new();

    for fill in fills {
        let entry = by_symbol
            .entry(fill.symbol.clone())
            .or_insert_with(|| SymbolSummary {
                symbol: fill.symbol.clone(),
                ..Default::default()
            });

        entry.total_trades += 1;
        entry.total_fees += fill.fee;

        if fill.side == Side::Bid {
            entry.buy_count += 1;
            entry.buy_volume += fill.quantity;
        } else {
            entry.sell_count += 1;
            entry.sell_volume += fill.quantity;
        }
    }

    // Second pass: compute averages and realized PnL
    for (symbol, entry) in by_symbol.iter_mut() {
        let symbol_fills: Vec<&Fill> = fills.iter().filter(|f| &f.symbol == symbol).collect();

        let mut total_buy_cost = Decimal::ZERO;
        let mut total_sell_cost = Decimal::ZERO;

        for fill in &symbol_fills {
            let cost = fill.price * fill.quantity;
            if fill.side == Side::Bid {
                total_buy_cost += cost;
            } else {
                total_sell_cost += cost;
            }
        }

        if entry.buy_volume > Decimal::ZERO {
            entry.avg_buy_price = total_buy_cost / entry.buy_volume;
        }
        if entry.sell_volume > Decimal::ZERO {
            entry.avg_sell_price = total_sell_cost / entry.sell_volume;
        }

        entry.total_volume = entry.buy_volume + entry.sell_volume;

        // Realized PnL: matched quantity × (avg_sell - avg_buy) − fees
        let matched_qty = entry.buy_volume.min(entry.sell_volume);
        if matched_qty > Decimal::ZERO
            && entry.avg_sell_price > Decimal::ZERO
            && entry.avg_buy_price > Decimal::ZERO
        {
            entry.realized_pnl =
                matched_qty * (entry.avg_sell_price - entry.avg_buy_price) - entry.total_fees;
        } else {
            entry.realized_pnl = -entry.total_fees;
        }
    }

    let mut summaries: Vec<SymbolSummary> = by_symbol.into_values().collect();
    summaries.sort_by(|a, b| b.total_volume.cmp(&a.total_volume));
    summaries
}

pub fn print_summary(fills: &[Fill], format: OutputFormat) {
    if fills.is_empty() {
        match format {
            OutputFormat::Json => super::print_json(&serde_json::json!({"summaries": []})),
            OutputFormat::Table => println!("No fills found."),
        }
        return;
    }

    let summaries = compute_summary(fills);

    match format {
        OutputFormat::Json => {
            super::print_json(&summaries);
        }
        OutputFormat::Table => {
            let rows: Vec<SummaryRow> = summaries
                .iter()
                .map(|sum| SummaryRow {
                    symbol: sum.symbol.clone(),
                    trades: sum.total_trades.to_string(),
                    buys: sum.buy_count.to_string(),
                    sells: sum.sell_count.to_string(),
                    volume: format!("{:.4}", sum.total_volume),
                    fees: format!("{:.4}", sum.total_fees),
                    avg_buy: if sum.avg_buy_price > Decimal::ZERO {
                        format!("{:.4}", sum.avg_buy_price)
                    } else {
                        "-".to_string()
                    },
                    avg_sell: if sum.avg_sell_price > Decimal::ZERO {
                        format!("{:.4}", sum.avg_sell_price)
                    } else {
                        "-".to_string()
                    },
                    pnl: format_pnl_value(sum.realized_pnl),
                })
                .collect();

            println!();
            println!("  {} fills across {} symbols", fills.len(), summaries.len());
            println!();

            let mut table = Table::new(rows);
            table.with(Style::blank());
            println!("{table}");
        }
    }
}
