use owo_colors::OwoColorize;
use serde::Serialize;
use tabled::settings::Style;
use tabled::{Table, Tabled};

use crate::output::OutputFormat;
use crate::style::Styles;

/// Data for a single row in the scan table.
#[derive(Debug, Clone, Serialize)]
pub struct ScanRow {
    pub asset: String,
    pub basis_bps: f64,
    pub funding_apr: f64,
    pub lending_apr: f64,
    pub total_apr: f64,
    pub volume_24h: f64,
    pub open_interest: f64,
    pub next_funding_mins: i64,
}

/// Data for a single row in the list table.
#[derive(Debug, Clone, Serialize)]
pub struct ListRow {
    pub asset: String,
    pub spot_qty: String,
    pub perp_qty: String,
    pub hedge_pct: String,
    pub net_pnl: String,
    pub total_apr: f64,
    pub earned: String,
}

/// Data for a detailed status view.
#[derive(Debug, Clone, Serialize)]
pub struct StatusData {
    pub asset: String,
    pub spot_symbol: String,
    pub perp_symbol: String,
    pub spot_qty: String,
    pub spot_value: String,
    pub perp_qty: String,
    pub perp_entry: String,
    pub perp_mark: String,
    pub perp_pnl_unrealized: String,
    pub perp_pnl_realized: String,
    pub funding_earned: String,
    pub lending_earned: String,
    pub hedge_ratio: String,
    pub current_funding_apr: f64,
    pub current_lending_apr: f64,
    pub total_apr: f64,
    pub margin_fraction: String,
}

/// Data for enter confirmation.
#[derive(Debug, Clone, Serialize)]
pub struct EnterPlan {
    pub asset: String,
    pub spot_symbol: String,
    pub perp_symbol: String,
    pub spot_qty: String,
    pub spot_price: String,
    pub spot_notional: String,
    pub perp_qty: String,
    pub auto_lend: bool,
    pub estimated_funding_apr: f64,
    pub estimated_lending_apr: f64,
    pub estimated_total_apr: f64,
}

#[derive(Tabled)]
struct ScanTableRow {
    #[tabled(rename = "ASSET")]
    asset: String,
    #[tabled(rename = "BASIS")]
    basis: String,
    #[tabled(rename = "FUND APR")]
    fund_apr: String,
    #[tabled(rename = "LEND APR")]
    lend_apr: String,
    #[tabled(rename = "TOTAL APR")]
    total_apr: String,
    #[tabled(rename = "24H VOL")]
    volume: String,
    #[tabled(rename = "OI")]
    oi: String,
    #[tabled(rename = "NEXT")]
    next: String,
}

#[derive(Tabled)]
struct ListTableRow {
    #[tabled(rename = "ASSET")]
    asset: String,
    #[tabled(rename = "SPOT QTY")]
    spot_qty: String,
    #[tabled(rename = "PERP QTY")]
    perp_qty: String,
    #[tabled(rename = "HEDGE")]
    hedge: String,
    #[tabled(rename = "NET PnL")]
    net_pnl: String,
    #[tabled(rename = "TOTAL APR")]
    total_apr: String,
    #[tabled(rename = "EARNED")]
    earned: String,
}

pub fn print_scan(rows: &[ScanRow], format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(rows),
        OutputFormat::Table => {
            if rows.is_empty() {
                println!("No basis opportunities found matching filters.");
                return;
            }
            let table_rows: Vec<ScanTableRow> = rows
                .iter()
                .map(|r| ScanTableRow {
                    asset: r.asset.clone(),
                    basis: format_bps(r.basis_bps),
                    fund_apr: format_apr(r.funding_apr),
                    lend_apr: format_apr(r.lending_apr),
                    total_apr: format_apr(r.total_apr),
                    volume: format_compact(r.volume_24h),
                    oi: format_compact(r.open_interest),
                    next: format_mins(r.next_funding_mins),
                })
                .collect();
            let mut table = Table::new(table_rows);
            table.with(Style::blank());
            println!("{table}");
        }
    }
}

pub fn print_list(rows: &[ListRow], format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(rows),
        OutputFormat::Table => {
            if rows.is_empty() {
                println!("No active basis trades detected.");
                return;
            }
            let table_rows: Vec<ListTableRow> = rows
                .iter()
                .map(|r| ListTableRow {
                    asset: r.asset.clone(),
                    spot_qty: r.spot_qty.clone(),
                    perp_qty: r.perp_qty.clone(),
                    hedge: r.hedge_pct.clone(),
                    net_pnl: r.net_pnl.clone(),
                    total_apr: format_apr(r.total_apr),
                    earned: r.earned.clone(),
                })
                .collect();
            let mut table = Table::new(table_rows);
            table.with(Style::blank());
            println!("{table}");
        }
    }
}

pub fn print_status(data: &StatusData, format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(data),
        OutputFormat::Table => {
            let s = Styles::new();
            println!();
            println!("  {} Basis Trade Status", data.asset.style(s.bold));
            println!();
            println!("  {} Spot Leg", "---".style(s.dim));
            println!("    Symbol:    {}", data.spot_symbol);
            println!("    Quantity:  {}", data.spot_qty);
            println!("    Value:     ${}", data.spot_value);
            println!();
            println!("  {} Perp Leg", "---".style(s.dim));
            println!("    Symbol:    {}", data.perp_symbol);
            println!("    Quantity:  {}", data.perp_qty);
            println!("    Entry:     {}", data.perp_entry);
            println!("    Mark:      {}", data.perp_mark);
            println!(
                "    Unreal PnL: {}",
                color_pnl(&data.perp_pnl_unrealized, &s)
            );
            println!("    Real PnL:   {}", color_pnl(&data.perp_pnl_realized, &s));
            println!();
            println!("  {} Earnings", "---".style(s.dim));
            println!("    Funding earned:  {}", data.funding_earned);
            println!("    Lending earned:  {}", data.lending_earned);
            println!();
            println!("  {} Metrics", "---".style(s.dim));
            println!("    Hedge ratio:     {}", data.hedge_ratio);
            println!(
                "    Funding APR:     {}",
                color_signed_str(
                    &format_apr(data.current_funding_apr),
                    data.current_funding_apr,
                    &s
                )
            );
            println!(
                "    Lending APR:     {}",
                color_signed_str(
                    &format_apr(data.current_lending_apr),
                    data.current_lending_apr,
                    &s
                )
            );
            println!(
                "    Total APR:       {}",
                color_signed_str(&format_apr(data.total_apr), data.total_apr, &s)
            );
            println!("    Margin fraction: {}", data.margin_fraction);
            println!();
        }
    }
}

pub fn print_enter_plan(plan: &EnterPlan, format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(plan),
        OutputFormat::Table => {
            let s = Styles::new();
            println!();
            println!("  {} Basis Trade Plan", plan.asset.style(s.bold));
            println!();
            println!(
                "    Spot BUY:  {} {} @ ~{} (~${})",
                plan.spot_qty.style(s.bold),
                plan.spot_symbol,
                plan.spot_price,
                plan.spot_notional,
            );
            println!(
                "    Perp SHORT: {} {}",
                plan.perp_qty.style(s.bold),
                plan.perp_symbol,
            );
            if plan.auto_lend {
                println!("    Auto-lend:  {}", "enabled".style(s.green));
            }
            println!();
            println!(
                "    Est. funding APR: {}",
                color_signed_str(
                    &format_apr(plan.estimated_funding_apr),
                    plan.estimated_funding_apr,
                    &s
                )
            );
            println!(
                "    Est. lending APR: {}",
                color_signed_str(
                    &format_apr(plan.estimated_lending_apr),
                    plan.estimated_lending_apr,
                    &s
                )
            );
            println!(
                "    Est. total APR:   {}",
                color_signed_str(
                    &format_apr(plan.estimated_total_apr),
                    plan.estimated_total_apr,
                    &s
                )
            );
            println!();
        }
    }
}

pub fn print_enter_success(asset: &str, spot_fill: &str, perp_fill: &str, format: OutputFormat) {
    match format {
        OutputFormat::Json => {
            super::print_json(&serde_json::json!({
                "status": "entered",
                "asset": asset,
                "spot_fill_qty": spot_fill,
                "perp_fill_qty": perp_fill,
            }));
        }
        OutputFormat::Table => {
            let s = Styles::new();
            println!(
                "{} Basis trade entered: {} (spot: {}, perp: {})",
                "\u{2713}".style(s.green),
                asset.style(s.bold),
                spot_fill,
                perp_fill,
            );
        }
    }
}

pub fn print_exit_success(asset: &str, pct: &str, format: OutputFormat) {
    match format {
        OutputFormat::Json => {
            super::print_json(&serde_json::json!({
                "status": "exited",
                "asset": asset,
                "percent": pct,
            }));
        }
        OutputFormat::Table => {
            let s = Styles::new();
            println!(
                "{} Basis trade exited: {} ({}%)",
                "\u{2713}".style(s.green),
                asset.style(s.bold),
                pct,
            );
        }
    }
}

// -- Formatting helpers --

fn format_bps(bps: f64) -> String {
    if bps >= 0.0 {
        format!("+{:.1}bp", bps)
    } else {
        format!("{:.1}bp", bps)
    }
}

fn format_apr(apr: f64) -> String {
    if apr >= 0.0 {
        format!("+{:.2}%", apr)
    } else {
        format!("{:.2}%", apr)
    }
}

fn format_compact(value: f64) -> String {
    if value.abs() >= 1_000_000_000.0 {
        format!("{:.1}B", value / 1_000_000_000.0)
    } else if value.abs() >= 1_000_000.0 {
        format!("{:.1}M", value / 1_000_000.0)
    } else if value.abs() >= 1_000.0 {
        format!("{:.1}K", value / 1_000.0)
    } else {
        format!("{:.1}", value)
    }
}

fn format_mins(mins: i64) -> String {
    if mins < 0 {
        "now".to_string()
    } else if mins < 60 {
        format!("{mins}m")
    } else {
        format!("{}h {}m", mins / 60, mins % 60)
    }
}

fn color_signed_str(s: &str, value: f64, styles: &Styles) -> String {
    if value > 0.0 {
        format!("{}", s.style(styles.green))
    } else if value < 0.0 {
        format!("{}", s.style(styles.red))
    } else {
        s.to_string()
    }
}

fn color_pnl(s: &str, styles: &Styles) -> String {
    if s.starts_with('-') {
        format!("{}", s.style(styles.red))
    } else if s.starts_with('+') {
        format!("{}", s.style(styles.green))
    } else {
        s.to_string()
    }
}
