use std::io::{IsTerminal, Read as _};
use std::str::FromStr;

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

use bpx_api_types::order::{
    CancelOpenOrdersPayload, ExecuteOrderPayload, OrderType, SelfTradePrevention, Side,
    SlippageToleranceType, TimeInForce, TriggerBy,
};

use crate::client;
use crate::config::ResolvedConfig;
use crate::output::order as out;
use crate::output::OutputFormat;
use crate::retry::{self, RetryConfig};

#[derive(Debug, Subcommand)]
pub enum OrderCommand {
    /// Place a buy order
    Buy {
        /// Market symbol (e.g. SOL_USDC)
        symbol: String,
        /// Order quantity
        quantity: String,
        /// Limit price (omit for market order)
        #[arg(long)]
        price: Option<String>,
        /// Time in force [GTC, IOC, FOK]
        #[arg(long, default_value = "GTC")]
        tif: String,
        /// Post-only order
        #[arg(long)]
        post_only: bool,
        /// Reduce-only order
        #[arg(long)]
        reduce_only: bool,
        /// Client order ID
        #[arg(long)]
        client_id: Option<u32>,
        /// Trigger price for stop-loss
        #[arg(long)]
        stop_loss: Option<String>,
        /// Trigger price for take-profit
        #[arg(long)]
        take_profit: Option<String>,
        /// Auto borrow
        #[arg(long)]
        auto_borrow: bool,
        /// Auto lend
        #[arg(long)]
        auto_lend: bool,
        /// Quote quantity (for market orders)
        #[arg(long)]
        quote_quantity: Option<String>,
        /// Trigger price for conditional orders
        #[arg(long)]
        trigger_price: Option<String>,
        /// Trigger reference [LastPrice, MarkPrice, IndexPrice]
        #[arg(long)]
        trigger_by: Option<String>,
        /// Self-trade prevention [RejectTaker, RejectMaker, RejectBoth, Allow]
        #[arg(long)]
        self_trade_prevention: Option<String>,
        /// Stop-loss limit price
        #[arg(long)]
        stop_loss_limit_price: Option<String>,
        /// Stop-loss trigger reference [LastPrice, MarkPrice, IndexPrice]
        #[arg(long)]
        stop_loss_trigger_by: Option<String>,
        /// Take-profit limit price
        #[arg(long)]
        take_profit_limit_price: Option<String>,
        /// Take-profit trigger reference [LastPrice, MarkPrice, IndexPrice]
        #[arg(long)]
        take_profit_trigger_by: Option<String>,
        /// Auto lend redeem
        #[arg(long)]
        auto_lend_redeem: bool,
        /// Auto borrow repay
        #[arg(long)]
        auto_borrow_repay: bool,
        /// Slippage tolerance
        #[arg(long)]
        slippage_tolerance: Option<String>,
        /// Slippage tolerance type [TickSize, Percent]
        #[arg(long)]
        slippage_tolerance_type: Option<String>,
    },

    /// Place a sell order
    Sell {
        /// Market symbol (e.g. SOL_USDC)
        symbol: String,
        /// Order quantity
        quantity: String,
        /// Limit price (omit for market order)
        #[arg(long)]
        price: Option<String>,
        /// Time in force [GTC, IOC, FOK]
        #[arg(long, default_value = "GTC")]
        tif: String,
        /// Post-only order
        #[arg(long)]
        post_only: bool,
        /// Reduce-only order
        #[arg(long)]
        reduce_only: bool,
        /// Client order ID
        #[arg(long)]
        client_id: Option<u32>,
        /// Trigger price for stop-loss
        #[arg(long)]
        stop_loss: Option<String>,
        /// Trigger price for take-profit
        #[arg(long)]
        take_profit: Option<String>,
        /// Auto borrow
        #[arg(long)]
        auto_borrow: bool,
        /// Auto lend
        #[arg(long)]
        auto_lend: bool,
        /// Quote quantity (for market orders)
        #[arg(long)]
        quote_quantity: Option<String>,
        /// Trigger price for conditional orders
        #[arg(long)]
        trigger_price: Option<String>,
        /// Trigger reference [LastPrice, MarkPrice, IndexPrice]
        #[arg(long)]
        trigger_by: Option<String>,
        /// Self-trade prevention [RejectTaker, RejectMaker, RejectBoth, Allow]
        #[arg(long)]
        self_trade_prevention: Option<String>,
        /// Stop-loss limit price
        #[arg(long)]
        stop_loss_limit_price: Option<String>,
        /// Stop-loss trigger reference [LastPrice, MarkPrice, IndexPrice]
        #[arg(long)]
        stop_loss_trigger_by: Option<String>,
        /// Take-profit limit price
        #[arg(long)]
        take_profit_limit_price: Option<String>,
        /// Take-profit trigger reference [LastPrice, MarkPrice, IndexPrice]
        #[arg(long)]
        take_profit_trigger_by: Option<String>,
        /// Auto lend redeem
        #[arg(long)]
        auto_lend_redeem: bool,
        /// Auto borrow repay
        #[arg(long)]
        auto_borrow_repay: bool,
        /// Slippage tolerance
        #[arg(long)]
        slippage_tolerance: Option<String>,
        /// Slippage tolerance type [TickSize, Percent]
        #[arg(long)]
        slippage_tolerance_type: Option<String>,
    },

    /// List open orders
    List {
        /// Filter by symbol
        #[arg(long)]
        symbol: Option<String>,
    },

    /// Get a specific open order
    Get {
        /// Market symbol (e.g. SOL_USDC)
        symbol: String,
        /// Order ID
        #[arg(long)]
        id: Option<String>,
        /// Client order ID
        #[arg(long)]
        client_id: Option<u32>,
    },

    /// Cancel an order
    Cancel {
        /// Market symbol
        symbol: String,
        /// Order ID to cancel
        #[arg(long)]
        id: Option<String>,
        /// Client order ID (alternative to --id)
        #[arg(long)]
        client_id: Option<u32>,
    },

    /// Cancel all open orders for a symbol
    CancelAll {
        /// Market symbol
        symbol: String,
    },

    /// Execute multiple orders from JSON (stdin or file)
    Batch {
        /// Path to JSON file with orders (reads stdin if omitted)
        #[arg(long)]
        file: Option<String>,
    },
}

#[allow(clippy::too_many_arguments)]
fn build_order_payload(
    symbol: &str,
    side: Side,
    quantity: &str,
    price: &Option<String>,
    tif: &str,
    post_only: bool,
    reduce_only: bool,
    client_id: Option<u32>,
    stop_loss: &Option<String>,
    take_profit: &Option<String>,
    auto_borrow: bool,
    auto_lend: bool,
    quote_quantity: &Option<String>,
    trigger_price: &Option<String>,
    trigger_by: &Option<String>,
    self_trade_prevention: &Option<String>,
    stop_loss_limit_price: &Option<String>,
    stop_loss_trigger_by: &Option<String>,
    take_profit_limit_price: &Option<String>,
    take_profit_trigger_by: &Option<String>,
    auto_lend_redeem: bool,
    auto_borrow_repay: bool,
    slippage_tolerance: &Option<String>,
    slippage_tolerance_type: &Option<String>,
) -> Result<ExecuteOrderPayload> {
    let order_type = if price.is_some() {
        OrderType::Limit
    } else {
        OrderType::Market
    };
    let time_in_force = parse_tif(tif)?;

    let qty =
        Decimal::from_str(quantity).map_err(|_| anyhow::anyhow!("Invalid quantity: {quantity}"))?;
    let price_dec = price
        .as_ref()
        .map(|p| Decimal::from_str(p))
        .transpose()
        .map_err(|_| anyhow::anyhow!("Invalid price: {}", price.as_deref().unwrap_or("")))?;
    let sl_price = stop_loss
        .as_ref()
        .map(|p| Decimal::from_str(p))
        .transpose()
        .map_err(|_| anyhow::anyhow!("Invalid stop-loss price"))?;
    let tp_price = take_profit
        .as_ref()
        .map(|p| Decimal::from_str(p))
        .transpose()
        .map_err(|_| anyhow::anyhow!("Invalid take-profit price"))?;

    // Parse new decimal fields
    let quote_qty = quote_quantity
        .as_ref()
        .map(|q| Decimal::from_str(q))
        .transpose()
        .map_err(|_| anyhow::anyhow!("Invalid quote quantity"))?;
    let trig_price = trigger_price
        .as_ref()
        .map(|p| Decimal::from_str(p))
        .transpose()
        .map_err(|_| anyhow::anyhow!("Invalid trigger price"))?;
    let sl_limit = stop_loss_limit_price
        .as_ref()
        .map(|p| Decimal::from_str(p))
        .transpose()
        .map_err(|_| anyhow::anyhow!("Invalid stop-loss limit price"))?;
    let tp_limit = take_profit_limit_price
        .as_ref()
        .map(|p| Decimal::from_str(p))
        .transpose()
        .map_err(|_| anyhow::anyhow!("Invalid take-profit limit price"))?;
    let slip_tol = slippage_tolerance
        .as_ref()
        .map(|p| Decimal::from_str(p))
        .transpose()
        .map_err(|_| anyhow::anyhow!("Invalid slippage tolerance"))?;

    // Parse enums
    let trig_by = trigger_by
        .as_ref()
        .map(|s| parse_trigger_by(s))
        .transpose()?;
    let stp = self_trade_prevention
        .as_ref()
        .map(|s| {
            s.parse::<SelfTradePrevention>().map_err(|_| {
                anyhow::anyhow!(
                    "Invalid self-trade-prevention '{s}'. Use RejectTaker, RejectMaker, RejectBoth, or Allow"
                )
            })
        })
        .transpose()?;
    let sl_trig = stop_loss_trigger_by
        .as_ref()
        .map(|s| parse_trigger_by(s))
        .transpose()?;
    let tp_trig = take_profit_trigger_by
        .as_ref()
        .map(|s| parse_trigger_by(s))
        .transpose()?;
    let slip_type = slippage_tolerance_type
        .as_ref()
        .map(|s| {
            s.parse::<SlippageToleranceType>().map_err(|_| {
                anyhow::anyhow!("Invalid slippage-tolerance-type '{s}'. Use TickSize or Percent")
            })
        })
        .transpose()?;

    Ok(ExecuteOrderPayload {
        symbol: symbol.to_string(),
        side,
        order_type,
        time_in_force: Some(time_in_force),
        quantity: Some(qty),
        price: price_dec,
        post_only: Some(post_only),
        reduce_only: Some(reduce_only),
        client_id,
        stop_loss_trigger_price: sl_price,
        take_profit_trigger_price: tp_price,
        auto_borrow: Some(auto_borrow),
        auto_lend: Some(auto_lend),
        quote_quantity: quote_qty,
        trigger_price: trig_price,
        trigger_by: trig_by,
        self_trade_prevention: stp,
        stop_loss_limit_price: sl_limit,
        stop_loss_trigger_by: sl_trig,
        take_profit_limit_price: tp_limit,
        take_profit_trigger_by: tp_trig,
        auto_lend_redeem: Some(auto_lend_redeem),
        auto_borrow_repay: Some(auto_borrow_repay),
        slippage_tolerance: slip_tol,
        slippage_tolerance_type: slip_type,
        ..Default::default()
    })
}

/// Perform dry-run validation for an order without executing it.
async fn dry_run_order(
    c: &bpx_api_client::BpxClient,
    payload: &ExecuteOrderPayload,
    format: OutputFormat,
) -> Result<()> {
    use bpx_api_types::account::MaxOrderQuery;

    let sp = client::spinner_for("Validating order (dry run)...", format);

    // Parallel fetch: max order + collateral + mark prices
    let side = payload.side;
    let symbol = payload.symbol.clone();
    let price = payload.price;

    let mut query = MaxOrderQuery::new(&symbol, side);
    if let Some(p) = price {
        query = query.with_price(p);
    }

    let (max_order_res, collateral_res, mark_prices_res) = tokio::join!(
        c.get_account_max_order(query),
        c.get_collateral(),
        c.get_all_mark_prices(),
    );
    sp.finish_and_clear();

    let max_order = max_order_res.map_err(|e| anyhow::anyhow!("{e}"))?;
    let collateral = collateral_res.map_err(|e| anyhow::anyhow!("{e}"))?;
    let mark_prices = mark_prices_res.map_err(|e| anyhow::anyhow!("{e}"))?;

    let order_qty = payload.quantity.unwrap_or(Decimal::ZERO);
    let max_qty = max_order.max_order_quantity;
    let qty_pass = order_qty <= max_qty;
    let utilization_pct = if max_qty > Decimal::ZERO {
        (order_qty / max_qty * Decimal::from(100)).round_dp(1)
    } else {
        Decimal::from(100)
    };

    // Margin impact estimation
    let current_mf = collateral.margin_fraction.unwrap_or(Decimal::ZERO);
    // Find mark price for this symbol
    let mark_price_val = mark_prices
        .iter()
        .find(|p| p.symbol == symbol)
        .map(|p| p.mark_price)
        .unwrap_or(Decimal::ZERO);

    let order_notional = order_qty * payload.price.unwrap_or(mark_price_val);
    let total_exposure = collateral.net_exposure_futures.abs() + order_notional;
    let net_equity = collateral.net_equity;
    let estimated_mf = if total_exposure > Decimal::ZERO && net_equity > Decimal::ZERO {
        (total_exposure / net_equity).round_dp(4)
    } else {
        current_mf
    };

    let margin_pass = estimated_mf < Decimal::ONE;
    let liq_distance = if estimated_mf > Decimal::ZERO {
        ((Decimal::ONE - estimated_mf) / Decimal::ONE * Decimal::from(100)).round_dp(1)
    } else {
        Decimal::from(100)
    };
    let liq_pass = liq_distance > Decimal::from(5);

    let valid = qty_pass && margin_pass && liq_pass;

    let mut warnings = Vec::new();
    if utilization_pct > Decimal::from(80) {
        warnings.push(format!(
            "Order would use {utilization_pct}% of max order capacity"
        ));
    }
    if estimated_mf > Decimal::from_str_exact("0.8").unwrap_or(Decimal::ZERO) {
        warnings.push(format!(
            "Order would increase margin utilization to {:.0}%",
            estimated_mf * Decimal::from(100)
        ));
    }

    let result = serde_json::json!({
        "valid": valid,
        "order": {
            "symbol": payload.symbol,
            "side": format!("{:?}", payload.side),
            "quantity": order_qty.to_string(),
            "price": payload.price.map(|p| p.to_string()),
            "order_type": format!("{:?}", payload.order_type),
        },
        "checks": {
            "quantity_within_max": {
                "pass": qty_pass,
                "max": max_qty.to_string(),
                "utilization_pct": utilization_pct.to_string(),
            },
            "margin_impact": {
                "pass": margin_pass,
                "current_mf": current_mf.to_string(),
                "estimated_mf_after": estimated_mf.to_string(),
            },
            "liquidation_risk": {
                "pass": liq_pass,
                "distance_after_pct": liq_distance.to_string(),
            },
        },
        "warnings": warnings,
    });

    match format {
        OutputFormat::Json => crate::output::print_json(&result),
        OutputFormat::Table => {
            let s = crate::style::Styles::new();
            println!();
            println!(
                "  {} Dry Run Validation",
                if valid {
                    "\u{2713}".style(s.green).to_string()
                } else {
                    "\u{2717}".style(s.red).to_string()
                }
            );
            println!();
            println!(
                "  Order: {} {} {} {}",
                format!("{:?}", payload.side).style(if payload.side == Side::Bid {
                    s.green
                } else {
                    s.red
                }),
                order_qty,
                payload.symbol,
                payload
                    .price
                    .map(|p| format!("@ {p}"))
                    .unwrap_or_else(|| "Market".to_string())
                    .style(s.dim),
            );
            println!();
            let check = |pass: bool| {
                if pass {
                    "\u{2713}".style(s.green).to_string()
                } else {
                    "\u{2717}".style(s.red).to_string()
                }
            };
            println!(
                "  {} Quantity within max: {order_qty} / {max_qty} ({utilization_pct}%)",
                check(qty_pass)
            );
            println!(
                "  {} Margin impact: {current_mf} \u{2192} {estimated_mf}",
                check(margin_pass)
            );
            println!(
                "  {} Liquidation distance: {liq_distance}%",
                check(liq_pass)
            );
            if !warnings.is_empty() {
                println!();
                for w in &warnings {
                    println!("  {} {w}", "!".style(s.yellow));
                }
            }
            println!();
        }
    }

    Ok(())
}

pub async fn run(
    cmd: &OrderCommand,
    config: &ResolvedConfig,
    secret: Option<&str>,
    base_url: Option<&str>,
    format: OutputFormat,
    retry_config: &RetryConfig,
    dry_run: bool,
) -> Result<()> {
    let c = client::authenticated_client(config, secret, base_url)?;
    let json_mode = format == OutputFormat::Json;

    match cmd {
        OrderCommand::Buy {
            symbol,
            quantity,
            price,
            tif,
            post_only,
            reduce_only,
            client_id,
            stop_loss,
            take_profit,
            auto_borrow,
            auto_lend,
            quote_quantity,
            trigger_price,
            trigger_by,
            self_trade_prevention,
            stop_loss_limit_price,
            stop_loss_trigger_by,
            take_profit_limit_price,
            take_profit_trigger_by,
            auto_lend_redeem,
            auto_borrow_repay,
            slippage_tolerance,
            slippage_tolerance_type,
        } => {
            let symbol = symbol.to_uppercase();
            let payload = build_order_payload(
                &symbol,
                Side::Bid,
                quantity,
                price,
                tif,
                *post_only,
                *reduce_only,
                *client_id,
                stop_loss,
                take_profit,
                *auto_borrow,
                *auto_lend,
                quote_quantity,
                trigger_price,
                trigger_by,
                self_trade_prevention,
                stop_loss_limit_price,
                stop_loss_trigger_by,
                take_profit_limit_price,
                take_profit_trigger_by,
                *auto_lend_redeem,
                *auto_borrow_repay,
                slippage_tolerance,
                slippage_tolerance_type,
            )?;
            if dry_run {
                return dry_run_order(&c, &payload, format).await;
            }
            let sp = client::spinner_for(&format!("Placing buy order on {symbol}..."), format);
            let payload_clone = payload.clone();
            let result = retry::with_retry(retry_config, json_mode, || {
                let p = payload_clone.clone();
                let client = &c;
                async move {
                    client
                        .execute_order(p)
                        .await
                        .map_err(|e| anyhow::anyhow!("{e}"))
                }
            })
            .await;
            sp.finish_and_clear();
            let order = result?;
            out::print_order(&order, format);
        }

        OrderCommand::Sell {
            symbol,
            quantity,
            price,
            tif,
            post_only,
            reduce_only,
            client_id,
            stop_loss,
            take_profit,
            auto_borrow,
            auto_lend,
            quote_quantity,
            trigger_price,
            trigger_by,
            self_trade_prevention,
            stop_loss_limit_price,
            stop_loss_trigger_by,
            take_profit_limit_price,
            take_profit_trigger_by,
            auto_lend_redeem,
            auto_borrow_repay,
            slippage_tolerance,
            slippage_tolerance_type,
        } => {
            let symbol = symbol.to_uppercase();
            let payload = build_order_payload(
                &symbol,
                Side::Ask,
                quantity,
                price,
                tif,
                *post_only,
                *reduce_only,
                *client_id,
                stop_loss,
                take_profit,
                *auto_borrow,
                *auto_lend,
                quote_quantity,
                trigger_price,
                trigger_by,
                self_trade_prevention,
                stop_loss_limit_price,
                stop_loss_trigger_by,
                take_profit_limit_price,
                take_profit_trigger_by,
                *auto_lend_redeem,
                *auto_borrow_repay,
                slippage_tolerance,
                slippage_tolerance_type,
            )?;
            if dry_run {
                return dry_run_order(&c, &payload, format).await;
            }
            let sp = client::spinner_for(&format!("Placing sell order on {symbol}..."), format);
            let payload_clone = payload.clone();
            let result = retry::with_retry(retry_config, json_mode, || {
                let p = payload_clone.clone();
                let client = &c;
                async move {
                    client
                        .execute_order(p)
                        .await
                        .map_err(|e| anyhow::anyhow!("{e}"))
                }
            })
            .await;
            sp.finish_and_clear();
            let order = result?;
            out::print_order(&order, format);
        }

        OrderCommand::List { symbol } => {
            let symbol = symbol.as_ref().map(|s| s.to_uppercase());
            let sp = client::spinner_for("Fetching open orders...", format);
            let result = c.get_open_orders(symbol.as_deref()).await;
            sp.finish_and_clear();
            let orders = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_orders(&orders, format);
        }

        OrderCommand::Get {
            symbol,
            id,
            client_id,
        } => {
            if id.is_none() && client_id.is_none() {
                anyhow::bail!("At least one of --id or --client-id is required");
            }
            let symbol = symbol.to_uppercase();
            let sp = client::spinner_for(&format!("Fetching order for {symbol}..."), format);
            let result = c.get_open_order(&symbol, id.as_deref(), *client_id).await;
            sp.finish_and_clear();
            let order = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_order(&order, format);
        }

        OrderCommand::Cancel {
            symbol,
            id,
            client_id,
        } => {
            if id.is_none() && client_id.is_none() {
                anyhow::bail!("At least one of --id or --client-id is required");
            }
            let symbol = symbol.to_uppercase();
            let label = id.as_deref().unwrap_or("(by client-id)").to_string();
            let sp = client::spinner_for(&format!("Cancelling order {label}..."), format);
            let result = c.cancel_order(&symbol, id.as_deref(), *client_id).await;
            sp.finish_and_clear();
            result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_cancel_success(&symbol, &label, format);
        }

        OrderCommand::CancelAll { symbol } => {
            let symbol = symbol.to_uppercase();

            // Confirm destructive operation (skip in JSON mode or non-TTY)
            if format == OutputFormat::Table && std::io::stdin().is_terminal() {
                use dialoguer::Confirm;
                let confirmed = Confirm::new()
                    .with_prompt(format!("Cancel ALL open orders on {symbol}?"))
                    .default(false)
                    .interact()?;
                if !confirmed {
                    println!("Cancelled.");
                    return Ok(());
                }
            }

            let payload = CancelOpenOrdersPayload {
                symbol: symbol.clone(),
            };
            let sp = client::spinner_for(&format!("Cancelling all orders on {symbol}..."), format);
            let result = c.cancel_open_orders(payload).await;
            sp.finish_and_clear();
            result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_cancel_all_success(&symbol, format);
        }

        OrderCommand::Batch { file } => {
            let json_str = match file {
                Some(path) => std::fs::read_to_string(path)
                    .map_err(|e| anyhow::anyhow!("Failed to read {path}: {e}"))?,
                None => {
                    if std::io::stdin().is_terminal() {
                        anyhow::bail!(
                            "No input provided. Use --file <path> or pipe JSON via stdin"
                        );
                    }
                    let mut buf = String::new();
                    std::io::stdin()
                        .read_to_string(&mut buf)
                        .map_err(|e| anyhow::anyhow!("Failed to read stdin: {e}"))?;
                    buf
                }
            };

            let payloads: Vec<ExecuteOrderPayload> = serde_json::from_str(&json_str)
                .map_err(|e| anyhow::anyhow!("Invalid JSON: {e}"))?;

            if payloads.is_empty() {
                anyhow::bail!("No orders provided");
            }

            let count = payloads.len();
            let sp = client::spinner_for(&format!("Executing {count} orders..."), format);
            let result = c.execute_orders(payloads).await;
            sp.finish_and_clear();
            let responses = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_batch_results(&responses, format);
        }
    }

    Ok(())
}

fn parse_tif(s: &str) -> Result<TimeInForce> {
    match s.to_uppercase().as_str() {
        "GTC" => Ok(TimeInForce::GTC),
        "IOC" => Ok(TimeInForce::IOC),
        "FOK" => Ok(TimeInForce::FOK),
        _ => anyhow::bail!("Invalid time-in-force '{s}'. Use GTC, IOC, or FOK"),
    }
}

fn parse_trigger_by(s: &str) -> Result<TriggerBy> {
    match s {
        "LastPrice" => Ok(TriggerBy::LastPrice),
        "MarkPrice" => Ok(TriggerBy::MarkPrice),
        "IndexPrice" => Ok(TriggerBy::IndexPrice),
        _ => anyhow::bail!("Invalid trigger-by '{s}'. Use LastPrice, MarkPrice, or IndexPrice"),
    }
}
