use std::str::FromStr;

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

use bpx_api_types::order::Side;
use bpx_api_types::rfq::{
    QuoteAcceptPayload, QuotePayload, RequestForQuoteCancelPayload, RequestForQuotePayload,
    RequestForQuoteRefreshPayload, RfqExecutionMode,
};

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

#[derive(Debug, Subcommand)]
pub enum RfqCommand {
    /// Submit a request for quote
    Submit {
        /// Market symbol (e.g. SOL_USDC)
        symbol: String,
        /// Side: buy/bid or sell/ask
        side: String,
        /// Quantity
        quantity: String,
        /// Quote quantity
        #[arg(long)]
        quote_quantity: Option<String>,
        /// Limit price
        #[arg(long)]
        price: Option<String>,
        /// Client-assigned ID
        #[arg(long)]
        client_id: Option<u32>,
        /// Execution mode [AwaitAccept, Immediate]
        #[arg(long)]
        execution_mode: Option<String>,
    },

    /// Cancel an RFQ
    Cancel {
        /// RFQ ID
        id: String,
        /// Client-assigned ID
        #[arg(long)]
        client_id: Option<u32>,
    },

    /// Refresh an RFQ
    Refresh {
        /// RFQ ID
        id: String,
    },

    /// Submit a quote for an RFQ
    Quote {
        /// RFQ ID
        rfq_id: String,
        /// Bid price
        bid_price: String,
        /// Ask price
        ask_price: String,
        /// Client-assigned ID
        #[arg(long)]
        client_id: Option<u32>,
        /// Auto lend
        #[arg(long)]
        auto_lend: bool,
        /// Auto lend redeem
        #[arg(long)]
        auto_lend_redeem: bool,
        /// Auto borrow
        #[arg(long)]
        auto_borrow: bool,
        /// Auto borrow repay
        #[arg(long)]
        auto_borrow_repay: bool,
    },

    /// Accept a quote
    Accept {
        /// RFQ ID
        rfq_id: String,
        /// Quote ID
        quote_id: String,
        /// Client-assigned ID
        #[arg(long)]
        client_id: Option<u32>,
    },
}

fn parse_side(s: &str) -> Result<Side> {
    match s.to_lowercase().as_str() {
        "buy" | "bid" => Ok(Side::Bid),
        "sell" | "ask" => Ok(Side::Ask),
        _ => Err(anyhow::anyhow!(
            "Invalid side '{s}'. Use buy/bid or sell/ask"
        )),
    }
}

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

    match cmd {
        RfqCommand::Submit {
            symbol,
            side,
            quantity,
            quote_quantity,
            price,
            client_id,
            execution_mode,
        } => {
            let symbol = symbol.to_uppercase();
            let side = parse_side(side)?;
            let qty = Decimal::from_str(quantity)
                .map_err(|_| anyhow::anyhow!("Invalid quantity: {quantity}"))?;
            let quote_qty = quote_quantity
                .as_ref()
                .map(|q| Decimal::from_str(q))
                .transpose()
                .map_err(|_| anyhow::anyhow!("Invalid quote quantity"))?;
            let price_dec = price
                .as_ref()
                .map(|p| Decimal::from_str(p))
                .transpose()
                .map_err(|_| anyhow::anyhow!("Invalid price"))?;
            let exec_mode = execution_mode
                .as_ref()
                .map(|m| {
                    m.parse::<RfqExecutionMode>().map_err(|_| {
                        anyhow::anyhow!(
                            "Invalid execution mode: '{m}'. Use AwaitAccept or Immediate"
                        )
                    })
                })
                .transpose()?;

            let payload = RequestForQuotePayload {
                client_id: *client_id,
                quantity: Some(qty),
                quote_quantity: quote_qty,
                price: price_dec,
                symbol: symbol.clone(),
                side,
                execution_mode: exec_mode,
            };

            let sp = client::spinner_for(&format!("Submitting RFQ for {symbol}..."), format);
            let result = c.submit_rfq(payload).await;
            sp.finish_and_clear();
            let rfq = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_rfq(&rfq, format);
        }

        RfqCommand::Cancel { id, client_id } => {
            let payload = RequestForQuoteCancelPayload {
                rfq_id: Some(id.clone()),
                client_id: *client_id,
            };

            let sp = client::spinner_for(&format!("Cancelling RFQ {id}..."), format);
            let result = c.cancel_rfq(payload).await;
            sp.finish_and_clear();
            let rfq = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_rfq(&rfq, format);
        }

        RfqCommand::Refresh { id } => {
            let payload = RequestForQuoteRefreshPayload { rfq_id: id.clone() };

            let sp = client::spinner_for(&format!("Refreshing RFQ {id}..."), format);
            let result = c.refresh_rfq(payload).await;
            sp.finish_and_clear();
            let rfq = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_rfq(&rfq, format);
        }

        RfqCommand::Quote {
            rfq_id,
            bid_price,
            ask_price,
            client_id,
            auto_lend,
            auto_lend_redeem,
            auto_borrow,
            auto_borrow_repay,
        } => {
            let bid = Decimal::from_str(bid_price)
                .map_err(|_| anyhow::anyhow!("Invalid bid price: {bid_price}"))?;
            let ask = Decimal::from_str(ask_price)
                .map_err(|_| anyhow::anyhow!("Invalid ask price: {ask_price}"))?;

            let mut payload = QuotePayload::new(rfq_id.clone(), bid, ask);
            if let Some(cid) = client_id {
                payload = payload.with_client_id(*cid);
            }
            if *auto_lend {
                payload = payload.with_auto_lend(true);
            }
            if *auto_lend_redeem {
                payload = payload.with_auto_lend_redeem(true);
            }
            if *auto_borrow {
                payload = payload.with_auto_borrow(true);
            }
            if *auto_borrow_repay {
                payload = payload.with_auto_borrow_repay(true);
            }

            let sp = client::spinner_for(&format!("Submitting quote for RFQ {rfq_id}..."), format);
            let result = c.submit_quote(payload).await;
            sp.finish_and_clear();
            let quote = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_quote(&quote, format);
        }

        RfqCommand::Accept {
            rfq_id,
            quote_id,
            client_id,
        } => {
            let payload = QuoteAcceptPayload {
                rfq_id: Some(rfq_id.clone()),
                client_id: *client_id,
                quote_id: quote_id.clone(),
            };

            let sp = client::spinner_for(&format!("Accepting quote {quote_id}..."), format);
            let result = c.accept_quote(payload).await;
            sp.finish_and_clear();
            let rfq = result.map_err(|e| anyhow::anyhow!("{e}"))?;
            out::print_rfq(&rfq, format);
        }
    }

    Ok(())
}
