use bpx_api_types::markets::{
    Asset, FundingRate, Kline, MarkPrice, Market, OrderBookDepth, Ticker,
};
use bpx_api_types::trade::Trade;
use owo_colors::OwoColorize;
use tabled::settings::Style;
use tabled::{Table, Tabled};

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

#[derive(Tabled)]
struct MarketRow {
    #[tabled(rename = "SYMBOL")]
    symbol: String,
    #[tabled(rename = "BASE")]
    base_symbol: String,
    #[tabled(rename = "QUOTE")]
    quote_symbol: String,
    #[tabled(rename = "TYPE")]
    market_type: String,
    #[tabled(rename = "TICK SIZE")]
    tick_size: String,
    #[tabled(rename = "STEP SIZE")]
    step_size: String,
    #[tabled(rename = "MIN QTY")]
    min_qty: String,
}

pub fn print_markets(markets: &[Market], format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(markets),
        OutputFormat::Table => {
            if markets.is_empty() {
                println!("No markets found.");
                return;
            }
            let rows: Vec<MarketRow> = markets
                .iter()
                .map(|m| MarketRow {
                    symbol: m.symbol.clone(),
                    base_symbol: m.base_symbol.clone(),
                    quote_symbol: m.quote_symbol.clone(),
                    market_type: m.market_type.clone(),
                    tick_size: m.filters.price.tick_size.to_string(),
                    step_size: m.filters.quantity.step_size.to_string(),
                    min_qty: m.filters.quantity.min_quantity.to_string(),
                })
                .collect();
            let mut table = Table::new(rows);
            table.with(Style::blank());
            println!("{table}");
        }
    }
}

#[derive(Tabled)]
struct TickerRow {
    #[tabled(rename = "SYMBOL")]
    symbol: String,
    #[tabled(rename = "LAST")]
    last_price: String,
    #[tabled(rename = "24H HIGH")]
    high: String,
    #[tabled(rename = "24H LOW")]
    low: String,
    #[tabled(rename = "24H VOL")]
    volume: String,
    #[tabled(rename = "24H CHANGE")]
    change: String,
}

pub fn print_ticker(ticker: &Ticker, format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(ticker),
        OutputFormat::Table => {
            print_tickers(std::slice::from_ref(ticker), format);
        }
    }
}

pub fn print_tickers(tickers: &[Ticker], format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(tickers),
        OutputFormat::Table => {
            if tickers.is_empty() {
                println!("No tickers found.");
                return;
            }
            let s = Styles::new();
            let rows: Vec<TickerRow> = tickers
                .iter()
                .map(|t| {
                    let pct = t.price_change_percent;
                    let change_str = format!("{:+.2}%", pct);
                    let change_display = if pct >= rust_decimal::Decimal::ZERO {
                        format!("{}", change_str.style(s.green))
                    } else {
                        format!("{}", change_str.style(s.red))
                    };

                    TickerRow {
                        symbol: t.symbol.clone(),
                        last_price: t.last_price.to_string(),
                        high: t.high.to_string(),
                        low: t.low.to_string(),
                        volume: t.volume.to_string(),
                        change: change_display,
                    }
                })
                .collect();
            let mut table = Table::new(rows);
            table.with(Style::blank());
            println!("{table}");
        }
    }
}

pub fn print_depth(depth: &OrderBookDepth, symbol: &str, format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(depth),
        OutputFormat::Table => {
            let s = Styles::new();
            println!("{}", format!("  Order Book: {symbol}").style(s.cyan));
            println!();

            // Asks (reversed so lowest ask is closest to spread)
            let asks: Vec<_> = depth.asks.iter().rev().collect();
            println!(
                "  {:>14}  {:>14}",
                "PRICE".style(s.dim),
                "SIZE".style(s.dim)
            );
            for (price, size) in &asks {
                println!("  {:>14}  {:>14}", price.to_string().style(s.red), size);
            }

            println!("  {}", "──────────────────────────────".style(s.dim));

            // Bids
            for (price, size) in &depth.bids {
                println!("  {:>14}  {:>14}", price.to_string().style(s.green), size);
            }
        }
    }
}

#[derive(Tabled)]
struct AssetRow {
    #[tabled(rename = "SYMBOL")]
    symbol: String,
    #[tabled(rename = "DISPLAY NAME")]
    display_name: String,
    #[tabled(rename = "CHAINS")]
    chains: String,
}

pub fn print_assets(assets: &[Asset], format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(assets),
        OutputFormat::Table => {
            if assets.is_empty() {
                println!("No assets found.");
                return;
            }
            let rows: Vec<AssetRow> = assets
                .iter()
                .map(|a| AssetRow {
                    symbol: a.symbol.clone(),
                    display_name: a.display_name.clone(),
                    chains: a
                        .tokens
                        .iter()
                        .map(|t| format!("{}", t.blockchain))
                        .collect::<Vec<_>>()
                        .join(", "),
                })
                .collect();
            let mut table = Table::new(rows);
            table.with(Style::blank());
            println!("{table}");
        }
    }
}

#[derive(Tabled)]
struct TradeRow {
    #[tabled(rename = "ID")]
    id: String,
    #[tabled(rename = "PRICE")]
    price: String,
    #[tabled(rename = "QTY")]
    quantity: String,
    #[tabled(rename = "QUOTE QTY")]
    quote_quantity: String,
    #[tabled(rename = "TIME")]
    time: String,
    #[tabled(rename = "SIDE")]
    side: String,
}

pub fn print_trades(trades: &[Trade], format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(trades),
        OutputFormat::Table => {
            if trades.is_empty() {
                println!("No trades found.");
                return;
            }
            let s = Styles::new();
            let rows: Vec<TradeRow> = trades
                .iter()
                .map(|t| {
                    let side = if t.is_buyer_maker { "SELL" } else { "BUY" };
                    let side_display = if t.is_buyer_maker {
                        format!("{}", side.style(s.red))
                    } else {
                        format!("{}", side.style(s.green))
                    };
                    TradeRow {
                        id: t.id.to_string(),
                        price: t.price.to_string(),
                        quantity: t.quantity.to_string(),
                        quote_quantity: t.quote_quantity.to_string(),
                        time: super::format_ts_ms(t.timestamp),
                        side: side_display,
                    }
                })
                .collect();
            let mut table = Table::new(rows);
            table.with(Style::blank());
            println!("{table}");
        }
    }
}

#[derive(Tabled)]
struct KlineRow {
    #[tabled(rename = "START")]
    start: String,
    #[tabled(rename = "OPEN")]
    open: String,
    #[tabled(rename = "HIGH")]
    high: String,
    #[tabled(rename = "LOW")]
    low: String,
    #[tabled(rename = "CLOSE")]
    close: String,
    #[tabled(rename = "VOLUME")]
    volume: String,
    #[tabled(rename = "TRADES")]
    trades: String,
}

pub fn print_klines(klines: &[Kline], format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(klines),
        OutputFormat::Table => {
            if klines.is_empty() {
                println!("No klines found.");
                return;
            }
            let rows: Vec<KlineRow> = klines
                .iter()
                .map(|k| KlineRow {
                    start: k.start.clone(),
                    open: k.open.map(|v| v.to_string()).unwrap_or_default(),
                    high: k.high.map(|v| v.to_string()).unwrap_or_default(),
                    low: k.low.map(|v| v.to_string()).unwrap_or_default(),
                    close: k.close.map(|v| v.to_string()).unwrap_or_default(),
                    volume: k.volume.to_string(),
                    trades: k.trades.clone(),
                })
                .collect();
            let mut table = Table::new(rows);
            table.with(Style::blank());
            println!("{table}");
        }
    }
}

#[derive(Tabled)]
struct FundingRateRow {
    #[tabled(rename = "SYMBOL")]
    symbol: String,
    #[tabled(rename = "TIMESTAMP")]
    timestamp: String,
    #[tabled(rename = "RATE")]
    rate: String,
}

pub fn print_funding_rates(rates: &[FundingRate], format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(rates),
        OutputFormat::Table => {
            if rates.is_empty() {
                println!("No funding rates found.");
                return;
            }
            let rows: Vec<FundingRateRow> = rates
                .iter()
                .map(|r| FundingRateRow {
                    symbol: r.symbol.clone(),
                    timestamp: r.interval_end_timestamp.clone(),
                    rate: r.funding_rate.to_string(),
                })
                .collect();
            let mut table = Table::new(rows);
            table.with(Style::blank());
            println!("{table}");
        }
    }
}

#[derive(Tabled)]
struct MarkPriceRow {
    #[tabled(rename = "SYMBOL")]
    symbol: String,
    #[tabled(rename = "MARK PRICE")]
    mark_price: String,
    #[tabled(rename = "INDEX PRICE")]
    index_price: String,
    #[tabled(rename = "FUNDING RATE")]
    funding_rate: String,
    #[tabled(rename = "NEXT FUNDING")]
    next_funding: String,
}

pub fn print_mark_prices(prices: &[MarkPrice], format: OutputFormat) {
    match format {
        OutputFormat::Json => super::print_json(prices),
        OutputFormat::Table => {
            if prices.is_empty() {
                println!("No mark prices found.");
                return;
            }
            let rows: Vec<MarkPriceRow> = prices
                .iter()
                .map(|p| MarkPriceRow {
                    symbol: p.symbol.clone(),
                    mark_price: p.mark_price.to_string(),
                    index_price: p.index_price.to_string(),
                    funding_rate: p.funding_rate.to_string(),
                    next_funding: super::format_ts_secs(p.next_funding_timestamp),
                })
                .collect();
            let mut table = Table::new(rows);
            table.with(Style::blank());
            println!("{table}");
        }
    }
}
