use anyhow::Result;
use clap::Subcommand;
use rust_decimal::Decimal;
use std::str::FromStr;

use bpx_api_types::account::{ConvertDustPayload, MaxOrderQuery, UpdateAccountPayload};
use bpx_api_types::order::Side;

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

#[derive(Debug, Subcommand)]
pub enum AccountCommand {
    /// Show account settings
    Info,

    /// Show account balances
    Balances,

    /// Show collateral summary and breakdown
    Collateral,

    /// Get maximum borrow quantity for an asset
    MaxBorrow {
        /// Asset symbol (e.g. USDC)
        symbol: String,
    },

    /// Get maximum order quantity
    MaxOrder {
        /// Market symbol (e.g. SOL_USDC)
        symbol: String,
        /// Order side: buy or sell
        side: String,
        /// Limit price
        #[arg(long)]
        price: Option<String>,
        /// Reduce-only order
        #[arg(long)]
        reduce_only: bool,
        /// Auto borrow
        #[arg(long)]
        auto_borrow: bool,
        /// Auto borrow repay
        #[arg(long)]
        auto_borrow_repay: bool,
        /// Auto lend redeem
        #[arg(long)]
        auto_lend_redeem: bool,
    },

    /// Get maximum withdrawal quantity for an asset
    MaxWithdrawal {
        /// Asset symbol (e.g. USDC)
        symbol: String,
        /// Auto borrow
        #[arg(long)]
        auto_borrow: bool,
        /// Auto lend redeem
        #[arg(long)]
        auto_lend_redeem: bool,
    },

    /// Update account settings
    Update {
        /// Enable/disable auto lend
        #[arg(long)]
        auto_lend: Option<bool>,
        /// Enable/disable auto repay borrows
        #[arg(long)]
        auto_repay: Option<bool>,
        /// Enable/disable auto borrow settlements
        #[arg(long)]
        auto_borrow: Option<bool>,
        /// Set leverage limit
        #[arg(long)]
        leverage_limit: Option<String>,
    },

    /// Convert dust balances to primary asset
    Dust {
        /// Asset symbol to convert (converts all dust if omitted)
        #[arg(long)]
        symbol: Option<String>,
    },
}

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

    match cmd {
        AccountCommand::Info => {
            let sp = client::spinner_for("Fetching account info...", format);
            let result = c.get_account().await;
            sp.finish_and_clear();
            let account = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_account(&account, format);
        }

        AccountCommand::Balances => {
            let sp = client::spinner_for("Fetching balances...", format);
            let result = c.get_balances().await;
            sp.finish_and_clear();
            let balances = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_balances(&balances, format);
        }

        AccountCommand::Collateral => {
            let sp = client::spinner_for("Fetching collateral...", format);
            let result = c.get_collateral().await;
            sp.finish_and_clear();
            let collateral = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_collateral(&collateral, format);
        }

        AccountCommand::MaxBorrow { symbol } => {
            let symbol = symbol.to_uppercase();
            let sp = client::spinner_for(&format!("Fetching max borrow for {symbol}..."), format);
            let result = c.get_account_max_borrow(&symbol).await;
            sp.finish_and_clear();
            let max_borrow = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_max_borrow(&max_borrow, format);
        }

        AccountCommand::MaxOrder {
            symbol,
            side,
            price,
            reduce_only,
            auto_borrow,
            auto_borrow_repay,
            auto_lend_redeem,
        } => {
            let symbol = symbol.to_uppercase();
            let side_enum = match side.to_lowercase().as_str() {
                "buy" | "bid" => Side::Bid,
                "sell" | "ask" => Side::Ask,
                _ => anyhow::bail!("Invalid side '{side}'. Use buy/bid or sell/ask"),
            };
            let mut query = MaxOrderQuery::new(&symbol, side_enum);
            if let Some(p) = price {
                let price_dec =
                    Decimal::from_str(p).map_err(|_| anyhow::anyhow!("Invalid price: {p}"))?;
                query = query.with_price(price_dec);
            }
            if *reduce_only {
                query = query.with_reduce_only(true);
            }
            if *auto_borrow {
                query = query.with_auto_borrow(true);
            }
            if *auto_borrow_repay {
                query = query.with_auto_borrow_repay(true);
            }
            if *auto_lend_redeem {
                query = query.with_auto_lend_redeem(true);
            }
            let sp = client::spinner_for(&format!("Fetching max order for {symbol}..."), format);
            let result = c.get_account_max_order(query).await;
            sp.finish_and_clear();
            let max_order = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_max_order(&max_order, format);
        }

        AccountCommand::MaxWithdrawal {
            symbol,
            auto_borrow,
            auto_lend_redeem,
        } => {
            let symbol = symbol.to_uppercase();
            let ab = if *auto_borrow { Some(true) } else { None };
            let alr = if *auto_lend_redeem { Some(true) } else { None };
            let sp =
                client::spinner_for(&format!("Fetching max withdrawal for {symbol}..."), format);
            let result = c.get_account_max_withdrawal(&symbol, ab, alr).await;
            sp.finish_and_clear();
            let max_withdrawal = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_max_withdrawal(&max_withdrawal, format);
        }

        AccountCommand::Update {
            auto_lend,
            auto_repay,
            auto_borrow,
            leverage_limit,
        } => {
            if auto_lend.is_none()
                && auto_repay.is_none()
                && auto_borrow.is_none()
                && leverage_limit.is_none()
            {
                anyhow::bail!("At least one of --auto-lend, --auto-repay, --auto-borrow, or --leverage-limit is required");
            }
            let lev = leverage_limit
                .as_ref()
                .map(|l| Decimal::from_str(l))
                .transpose()
                .map_err(|_| {
                    anyhow::anyhow!(
                        "Invalid leverage limit: {}",
                        leverage_limit.as_deref().unwrap_or("")
                    )
                })?;
            let payload = UpdateAccountPayload {
                auto_lend: *auto_lend,
                auto_repay_borrows: *auto_repay,
                auto_borrow_settlements: *auto_borrow,
                leverage_limit: lev,
            };
            let sp = client::spinner_for("Updating account...", format);
            let result = c.update_account(payload).await;
            sp.finish_and_clear();
            result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_update_success(format);
        }

        AccountCommand::Dust { symbol } => {
            let payload = ConvertDustPayload {
                symbol: symbol.as_ref().map(|s| s.to_uppercase()),
            };
            let sp = client::spinner_for("Converting dust balances...", format);
            let result = c.convert_dust_balance(payload).await;
            sp.finish_and_clear();
            result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_dust_success(format);
        }
    }

    Ok(())
}
