use std::collections::HashMap;
use std::io::IsTerminal;

use anyhow::Result;
use owo_colors::OwoColorize;
use rust_decimal::Decimal;
use serde::Serialize;
use tabled::settings::Style;
use tabled::{Table, Tabled};

use bpx_api_types::account::{AccountSettings, ConvertDustPayload, UpdateAccountPayload};
use bpx_api_types::borrow_lend::BorrowLendPosition;
use bpx_api_types::capital::{Balance, Collateral};
use bpx_api_types::markets::MarkPrice;

use crate::client;
use crate::config::ResolvedConfig;
use crate::output::OutputFormat;
use crate::style::Styles;

#[derive(Debug, Clone, Serialize)]
pub struct Suggestion {
    pub priority: u32,
    pub action: String,
    pub description: String,
    pub estimated_annual_yield: Decimal,
    pub risk: &'static str,
}

/// Analyze the account and produce optimization suggestions.
pub fn analyze(
    balances: &HashMap<String, Balance>,
    collateral: &Collateral,
    account_settings: &AccountSettings,
    borrow_lend_positions: &[BorrowLendPosition],
    mark_prices: &[MarkPrice],
) -> Vec<Suggestion> {
    let mut suggestions = Vec::new();

    // Build mark price lookup
    let price_map: HashMap<&str, Decimal> = mark_prices
        .iter()
        .map(|mp| (mp.symbol.as_str(), mp.mark_price))
        .collect();

    // 1. Enable auto-lend for idle balances
    if !account_settings.auto_lend {
        let lent_symbols: std::collections::HashSet<&str> = borrow_lend_positions
            .iter()
            .filter(|p| p.net_quantity > Decimal::ZERO)
            .map(|p| p.symbol.as_str())
            .collect();

        let mut idle_total = Decimal::ZERO;
        for (symbol, balance) in balances {
            if balance.available > Decimal::ZERO && !lent_symbols.contains(symbol.as_str()) {
                // Estimate notional value
                let price = find_asset_price(symbol, &price_map);
                idle_total += balance.available * price;
            }
        }

        if idle_total > Decimal::from(10) {
            suggestions.push(Suggestion {
                priority: 1,
                action: "Enable auto-lend".to_string(),
                description: format!(
                    "~${} idle balance could earn lending yield. Run: bpx account update --auto-lend true",
                    idle_total.round_dp(0)
                ),
                estimated_annual_yield: idle_total * Decimal::new(5, 2), // ~5% APY estimate
                risk: "none",
            });
        }
    }

    // 2. Convert dust balances (< $5 notional)
    let mut dust_assets = Vec::new();
    for (symbol, balance) in balances {
        if symbol == "USDC" {
            continue;
        }
        let total = balance.total();
        if total > Decimal::ZERO {
            let price = find_asset_price(symbol, &price_map);
            let notional = total * price;
            if notional < Decimal::from(5) && notional > Decimal::ZERO {
                dust_assets.push(symbol.clone());
            }
        }
    }
    if !dust_assets.is_empty() {
        suggestions.push(Suggestion {
            priority: 3,
            action: "Convert dust to USDC".to_string(),
            description: format!(
                "{} dust balance(s): {}. Run: bpx account dust",
                dust_assets.len(),
                dust_assets.join(", ")
            ),
            estimated_annual_yield: Decimal::ZERO,
            risk: "none",
        });
    }

    // 3. Reserve margin buffer when utilization > 80%
    if let Some(mf) = collateral.margin_fraction {
        if mf > Decimal::ZERO {
            let utilization = collateral.mmf / mf * Decimal::from(100);
            if utilization > Decimal::from(80) {
                suggestions.push(Suggestion {
                    priority: 1,
                    action: "Reduce margin utilization".to_string(),
                    description: format!(
                        "Margin utilization at {:.1}% - close to liquidation threshold. Consider reducing position sizes or adding collateral.",
                        utilization
                    ),
                    estimated_annual_yield: Decimal::ZERO,
                    risk: "none",
                });
            }
        }
    }

    // 4. Flag concentration risk (single asset > 50% of collateral)
    for item in &collateral.collateral {
        if collateral.assets_value > Decimal::ZERO {
            let pct = item.balance_notional / collateral.assets_value * Decimal::from(100);
            if pct > Decimal::from(50) && item.symbol != "USDC" {
                suggestions.push(Suggestion {
                    priority: 2,
                    action: format!("Diversify {} collateral", item.symbol),
                    description: format!(
                        "{} represents {:.1}% of total collateral value. Consider diversifying to reduce single-asset risk.",
                        item.symbol, pct
                    ),
                    estimated_annual_yield: Decimal::ZERO,
                    risk: "low",
                });
            }
        }
    }

    suggestions.sort_by_key(|s| s.priority);
    suggestions
}

/// Find the USD price for an asset symbol by checking mark prices.
/// Falls back to 1.0 for stablecoins, 0.0 for unknown.
fn find_asset_price(symbol: &str, price_map: &HashMap<&str, Decimal>) -> Decimal {
    // Check for direct _USDC or _USDC_PERP mark price
    let spot_key = format!("{symbol}_USDC");
    let perp_key = format!("{symbol}_USDC_PERP");

    if let Some(&price) = price_map.get(perp_key.as_str()) {
        return price;
    }
    if let Some(&price) = price_map.get(spot_key.as_str()) {
        return price;
    }

    // Stablecoins default to 1
    match symbol {
        "USDC" | "USDT" | "PYUSD" => Decimal::ONE,
        _ => Decimal::ZERO,
    }
}

pub async fn run(
    config: &ResolvedConfig,
    secret: Option<&str>,
    base_url: Option<&str>,
    format: OutputFormat,
    apply: bool,
) -> Result<()> {
    let c = client::authenticated_client(config, secret, base_url)?;
    let c2 = client::authenticated_client(config, secret, base_url)?;

    let sp = client::spinner_for("Analyzing collateral...", format);
    let (balances_res, collateral_res, account_res, borrow_res, mark_res) = tokio::join!(
        c.get_balances(),
        c.get_collateral(),
        c2.get_account(),
        c2.get_borrow_lend_positions(),
        c.get_all_mark_prices(),
    );
    sp.finish_and_clear();

    let balances = balances_res.map_err(|e| anyhow::anyhow!("{e}"))?;
    let collateral = collateral_res.map_err(|e| anyhow::anyhow!("{e}"))?;
    let account = account_res.map_err(|e| anyhow::anyhow!("{e}"))?;
    let borrow_lend = borrow_res.map_err(|e| anyhow::anyhow!("{e}"))?;
    let mark_prices = mark_res.map_err(|e| anyhow::anyhow!("{e}"))?;

    let suggestions = analyze(&balances, &collateral, &account, &borrow_lend, &mark_prices);

    match format {
        OutputFormat::Json => {
            crate::output::print_json(&serde_json::json!({
                "suggestions": suggestions,
                "count": suggestions.len(),
            }));
        }
        OutputFormat::Table => {
            let s = Styles::new();
            println!();
            println!("  {}", "COLLATERAL OPTIMIZER".style(s.cyan));
            println!();

            if suggestions.is_empty() {
                println!(
                    "  {} No optimizations found - your collateral looks good!",
                    "\u{2713}".style(s.green)
                );
                println!();
                return Ok(());
            }

            #[derive(Tabled)]
            struct SuggestionRow {
                #[tabled(rename = "#")]
                priority: String,
                #[tabled(rename = "ACTION")]
                action: String,
                #[tabled(rename = "RISK")]
                risk: String,
                #[tabled(rename = "EST. YIELD")]
                est_yield: String,
            }

            let rows: Vec<SuggestionRow> = suggestions
                .iter()
                .enumerate()
                .map(|(i, sg)| {
                    let risk_str = match sg.risk {
                        "none" => "none".to_string(),
                        "low" => "low".style(s.yellow).to_string(),
                        "medium" => "medium".style(s.red).to_string(),
                        other => other.to_string(),
                    };

                    let yield_str = if sg.estimated_annual_yield > Decimal::ZERO {
                        format!("~${}/yr", sg.estimated_annual_yield.round_dp(0))
                    } else {
                        "-".to_string()
                    };

                    SuggestionRow {
                        priority: format!("{}", i + 1),
                        action: sg.action.clone(),
                        risk: risk_str,
                        est_yield: yield_str,
                    }
                })
                .collect();

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

            // Print descriptions
            for (i, sg) in suggestions.iter().enumerate() {
                println!(
                    "  {} {}",
                    format!("{}.", i + 1).style(s.bold),
                    sg.description
                );
            }
            println!();
        }
    }

    // --apply: execute safe automated actions (auto-lend + dust conversion)
    if apply {
        let has_auto_lend = suggestions.iter().any(|s| s.action == "Enable auto-lend");
        let has_dust = suggestions
            .iter()
            .any(|s| s.action == "Convert dust to USDC");

        if !has_auto_lend && !has_dust {
            if format == OutputFormat::Table {
                println!("  No auto-applicable suggestions found.");
                println!();
            }
            return Ok(());
        }

        // Confirm in interactive table mode
        if format == OutputFormat::Table && std::io::stdin().is_terminal() {
            let mut actions = Vec::new();
            if has_auto_lend {
                actions.push("enable auto-lend");
            }
            if has_dust {
                actions.push("convert dust to USDC");
            }
            let prompt = format!("Apply: {}?", actions.join(" + "));
            let confirmed = dialoguer::Confirm::new()
                .with_prompt(prompt)
                .default(false)
                .interact()?;
            if !confirmed {
                println!("Cancelled.");
                return Ok(());
            }
        }

        let c3 = client::authenticated_client(config, secret, base_url)?;

        if has_auto_lend {
            let sp = client::spinner_for("Enabling auto-lend...", format);
            let payload = UpdateAccountPayload {
                auto_lend: Some(true),
                ..Default::default()
            };
            let result = c3.update_account(payload).await;
            sp.finish_and_clear();
            result.map_err(|e| anyhow::anyhow!("{e}"))?;
            match format {
                OutputFormat::Json => {
                    crate::output::print_json(
                        &serde_json::json!({"applied": "auto_lend", "status": "enabled"}),
                    );
                }
                OutputFormat::Table => {
                    let s = Styles::new();
                    println!("  {} Auto-lend enabled", "\u{2713}".style(s.green));
                }
            }
        }

        if has_dust {
            let sp = client::spinner_for("Converting dust balances...", format);
            let payload = ConvertDustPayload { symbol: None };
            let result = c3.convert_dust_balance(payload).await;
            sp.finish_and_clear();
            result.map_err(|e| anyhow::anyhow!("{e}"))?;
            match format {
                OutputFormat::Json => {
                    crate::output::print_json(
                        &serde_json::json!({"applied": "dust_convert", "status": "converted"}),
                    );
                }
                OutputFormat::Table => {
                    let s = Styles::new();
                    println!("  {} Dust balances converted", "\u{2713}".style(s.green));
                }
            }
        }

        if format == OutputFormat::Table {
            println!();
        }
    }

    Ok(())
}
