use std::collections::HashMap;

use anyhow::Result;
use clap::Subcommand;
use rust_decimal::Decimal;

use crate::client;
use crate::config::ResolvedConfig;
use crate::output::risk as out;
use crate::output::OutputFormat;
use crate::risk;

#[derive(Debug, Subcommand)]
pub enum RiskCommand {
    /// Portfolio risk overview with key metrics
    Summary,

    /// Run stress test scenarios against current positions
    Stress {
        /// Pre-built scenario name (e.g. "BTC -10%", "All Crypto -20%")
        #[arg(long)]
        scenario: Option<String>,

        /// Custom price moves (e.g. "SOL:-15,BTC:-10")
        #[arg(long)]
        custom: Option<String>,
    },
}

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

    match cmd {
        RiskCommand::Summary => {
            let sp = client::spinner_for("Fetching risk data...", format);
            let (collateral_res, positions_res, mark_prices_res) = tokio::join!(
                c.get_collateral(),
                c.get_open_future_positions(),
                c.get_all_mark_prices(),
            );
            sp.finish_and_clear();

            let collateral = collateral_res.map_err(|e| anyhow::anyhow!("{e}"))?;
            let positions = positions_res.map_err(|e| anyhow::anyhow!("{e}"))?;
            let mark_prices_vec = mark_prices_res.map_err(|e| anyhow::anyhow!("{e}"))?;

            let mark_prices: HashMap<String, Decimal> = mark_prices_vec
                .iter()
                .map(|mp| (mp.symbol.clone(), mp.mark_price))
                .collect();

            let utilization = risk::margin_utilization(&collateral);
            let total_exposure = collateral.net_exposure_futures;
            let largest = risk::largest_exposure(&positions, &mark_prices);
            let hhi = risk::concentration_hhi(&positions, &mark_prices);

            out::print_summary(
                &collateral,
                utilization,
                total_exposure,
                largest.as_ref(),
                hhi,
                format,
            );
        }

        RiskCommand::Stress { scenario, custom } => {
            let c2 = client::authenticated_client(config, secret, base_url)?;
            let sp = client::spinner_for("Fetching risk data...", format);
            let (collateral_res, positions_res, mark_prices_res) = tokio::join!(
                c.get_collateral(),
                c.get_open_future_positions(),
                c2.get_all_mark_prices(),
            );
            sp.finish_and_clear();

            let collateral = collateral_res.map_err(|e| anyhow::anyhow!("{e}"))?;
            let positions = positions_res.map_err(|e| anyhow::anyhow!("{e}"))?;
            let mark_prices_vec = mark_prices_res.map_err(|e| anyhow::anyhow!("{e}"))?;

            let mark_prices: HashMap<String, Decimal> = mark_prices_vec
                .iter()
                .map(|mp| (mp.symbol.clone(), mp.mark_price))
                .collect();

            if positions.is_empty() {
                match format {
                    OutputFormat::Json => {
                        crate::output::print_json(
                            &serde_json::json!({"stress_tests": [], "note": "No open positions"}),
                        );
                    }
                    OutputFormat::Table => {
                        println!("No open futures positions to stress test.");
                    }
                }
                return Ok(());
            }

            let scenarios = build_scenarios(&positions, scenario.as_deref(), custom.as_deref())?;

            let mut results = Vec::new();
            for (name, moves) in &scenarios {
                let result = risk::stress_test(&positions, &collateral, &mark_prices, moves, name);
                results.push(result);
            }

            out::print_stress_tests(&results, format);
        }
    }

    Ok(())
}

fn build_scenarios(
    positions: &[bpx_api_types::futures::FuturePosition],
    scenario_filter: Option<&str>,
    custom: Option<&str>,
) -> Result<Vec<(String, HashMap<String, Decimal>)>> {
    let mut scenarios = Vec::new();

    if let Some(custom_str) = custom {
        let moves = risk::parse_custom_scenario(custom_str)?;
        scenarios.push(("Custom".to_string(), moves));
        return Ok(scenarios);
    }

    let builtins = risk::builtin_scenarios();

    for (name, moves) in builtins {
        if let Some(filter) = scenario_filter {
            if !name.to_lowercase().contains(&filter.to_lowercase()) {
                continue;
            }
        }

        let expanded = if name == "All Crypto -20%" {
            risk::expand_all_crypto(positions)
        } else if name == "Stablecoin depeg -5%" {
            risk::expand_stablecoin_depeg(positions)
        } else {
            moves
        };

        scenarios.push((name.to_string(), expanded));
    }

    if scenarios.is_empty() {
        if let Some(filter) = scenario_filter {
            anyhow::bail!(
                "No scenario matching '{filter}'. Available: BTC -10%, BTC -25%, All Crypto -20%, Stablecoin depeg -5%"
            );
        }
    }

    Ok(scenarios)
}
