use anyhow::Result;
use clap::Subcommand;

use bpx_api_types::markets::OrderBookDepthLimit;

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

#[derive(Debug, Subcommand)]
pub enum MarketsCommand {
    /// List all available markets
    List {
        /// Filter by market type (e.g. SPOT, PERP)
        #[arg(long)]
        r#type: Option<String>,
    },

    /// Get 24h ticker for a symbol
    Ticker {
        /// Market symbol (e.g. SOL_USDC)
        symbol: String,
    },

    /// Get all tickers
    Tickers,

    /// Show order book depth
    Depth {
        /// Market symbol (e.g. SOL_USDC)
        symbol: String,

        /// Number of levels [5, 10, 20, 50, 100, 500, 1000]
        #[arg(long, default_value = "10")]
        limit: u32,
    },

    /// List all assets
    Assets,

    /// Get recent trades for a symbol
    Trades {
        /// Market symbol (e.g. SOL_USDC)
        symbol: String,

        /// Maximum number of trades to return
        #[arg(long)]
        limit: Option<i16>,
    },

    /// Get historical trades for a symbol
    History {
        /// Market symbol (e.g. SOL_USDC)
        symbol: String,

        /// Maximum number of trades to return
        #[arg(long)]
        limit: Option<i64>,

        /// Offset for pagination
        #[arg(long)]
        offset: Option<i64>,
    },

    /// Get kline/candlestick data
    Klines {
        /// Market symbol (e.g. SOL_USDC)
        symbol: String,

        /// Kline interval [1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M]
        #[arg(long)]
        interval: String,

        /// Start time (unix timestamp in seconds)
        #[arg(long)]
        start: i64,

        /// End time (unix timestamp in seconds)
        #[arg(long)]
        end: Option<i64>,
    },

    /// Get funding interval rates for a perpetual market
    Funding {
        /// Market symbol (e.g. SOL_USDC_PERP)
        symbol: String,
    },

    /// Get all mark prices
    MarkPrices,
}

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

    match cmd {
        MarketsCommand::List { r#type } => {
            let sp = client::spinner_for("Fetching markets...", format);
            let result = c.get_markets().await;
            sp.finish_and_clear();
            let mut markets = result.map_err(|e| anyhow::anyhow!("{e}"))?;

            if let Some(filter) = r#type {
                let filter_upper = filter.to_uppercase();
                markets.retain(|m| m.market_type.to_uppercase() == filter_upper);
            }

            out::print_markets(&markets, format);
        }

        MarketsCommand::Ticker { symbol } => {
            let symbol = symbol.to_uppercase();
            let sp = client::spinner_for(&format!("Fetching ticker for {symbol}..."), format);
            let result = c.get_ticker(&symbol).await;
            sp.finish_and_clear();
            let ticker = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_ticker(&ticker, format);
        }

        MarketsCommand::Tickers => {
            let sp = client::spinner_for("Fetching all tickers...", format);
            let result = c.get_tickers().await;
            sp.finish_and_clear();
            let tickers = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_tickers(&tickers, format);
        }

        MarketsCommand::Depth { symbol, limit } => {
            let symbol = symbol.to_uppercase();
            let depth_limit = OrderBookDepthLimit::try_from(*limit).map_err(|_| {
                anyhow::anyhow!(
                    "Invalid depth limit {limit}. Allowed: 5, 10, 20, 50, 100, 500, 1000"
                )
            })?;
            let sp = client::spinner_for(&format!("Fetching depth for {symbol}..."), format);
            let result = c.get_order_book_depth(&symbol, Some(depth_limit)).await;
            sp.finish_and_clear();
            let depth = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_depth(&depth, &symbol, format);
        }

        MarketsCommand::Assets => {
            let sp = client::spinner_for("Fetching assets...", format);
            let result = c.get_assets().await;
            sp.finish_and_clear();
            let assets = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_assets(&assets, format);
        }

        MarketsCommand::Trades { symbol, limit } => {
            let symbol = symbol.to_uppercase();
            let sp =
                client::spinner_for(&format!("Fetching recent trades for {symbol}..."), format);
            let result = c.get_recent_trades(&symbol, *limit).await;
            sp.finish_and_clear();
            let trades = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_trades(&trades, format);
        }

        MarketsCommand::History {
            symbol,
            limit,
            offset,
        } => {
            let symbol = symbol.to_uppercase();
            let sp = client::spinner_for(
                &format!("Fetching historical trades for {symbol}..."),
                format,
            );
            let result = c.get_historical_trades(&symbol, *limit, *offset).await;
            sp.finish_and_clear();
            let trades = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_trades(&trades, format);
        }

        MarketsCommand::Klines {
            symbol,
            interval,
            start,
            end,
        } => {
            let symbol = symbol.to_uppercase();
            let sp = client::spinner_for(&format!("Fetching klines for {symbol}..."), format);
            let result = c.get_k_lines(&symbol, interval, *start, *end).await;
            sp.finish_and_clear();
            let klines = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_klines(&klines, format);
        }

        MarketsCommand::Funding { symbol } => {
            let symbol = symbol.to_uppercase();
            let sp =
                client::spinner_for(&format!("Fetching funding rates for {symbol}..."), format);
            let result = c.get_funding_interval_rates(&symbol).await;
            sp.finish_and_clear();
            let rates = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_funding_rates(&rates, format);
        }

        MarketsCommand::MarkPrices => {
            let sp = client::spinner_for("Fetching mark prices...", format);
            let result = c.get_all_mark_prices().await;
            sp.finish_and_clear();
            let prices = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_mark_prices(&prices, format);
        }
    }

    Ok(())
}
