use bpx_api_types::order::{ExecuteOrderPayload, OrderType, Side, TimeInForce};
use rust_decimal::Decimal;
use std::str::FromStr;

// ── Parsed types ────────────────────────────────────────────────────

#[derive(Debug, Clone, PartialEq)]
pub enum SizeSpec {
    Units(Decimal),
    Dollars(Decimal),
    Percent(Decimal),
}

#[derive(Debug, Clone, PartialEq)]
pub enum PriceSpec {
    Absolute(Decimal),
    Offset(Decimal),
    PercentOffset(Decimal),
    Best,
}

#[derive(Debug, Clone, PartialEq)]
pub enum TradeCommand {
    Order {
        side: Side,
        size: SizeSpec,
        price: Option<PriceSpec>,
        stop: Option<PriceSpec>,
        reduce_only: bool,
    },
    Close {
        percent: Option<Decimal>,
    },
    NukeOrders,
    NukePositions,
    NukeAll,
}

// ── Parser ──────────────────────────────────────────────────────────

pub fn parse_trade_command(input: &str) -> Option<TradeCommand> {
    let tokens: Vec<&str> = input.split_whitespace().collect();
    if tokens.is_empty() {
        return None;
    }

    match tokens[0].to_lowercase().as_str() {
        "buy" | "sell" => parse_order(&tokens),
        "close" => parse_close(&tokens),
        "nuke" => parse_nuke(&tokens),
        "flatten" => Some(TradeCommand::NukeAll),
        _ => None,
    }
}

fn parse_order(tokens: &[&str]) -> Option<TradeCommand> {
    let side = match tokens[0].to_lowercase().as_str() {
        "buy" => Side::Bid,
        "sell" => Side::Ask,
        _ => return None,
    };

    if tokens.len() < 2 {
        return None;
    }

    let size = parse_size_spec(tokens[1])?;

    let mut price = None;
    let mut stop = None;
    let mut reduce_only = false;
    let mut i = 2;

    while i < tokens.len() {
        match tokens[i].to_lowercase().as_str() {
            "at" => {
                i += 1;
                if i >= tokens.len() {
                    return None;
                }
                price = Some(parse_price_spec(tokens[i])?);
            }
            "stop" => {
                i += 1;
                if i >= tokens.len() {
                    return None;
                }
                stop = Some(parse_price_spec(tokens[i])?);
            }
            "reduce" => {
                reduce_only = true;
            }
            "best" => {
                price = Some(PriceSpec::Best);
            }
            _ => return None,
        }
        i += 1;
    }

    Some(TradeCommand::Order {
        side,
        size,
        price,
        stop,
        reduce_only,
    })
}

fn parse_close(tokens: &[&str]) -> Option<TradeCommand> {
    if tokens.len() == 1 {
        return Some(TradeCommand::Close { percent: None });
    }
    if tokens.len() == 2 {
        let arg = tokens[1];
        if let Some(pct_str) = arg.strip_suffix('%') {
            let pct = Decimal::from_str(pct_str).ok()?;
            return Some(TradeCommand::Close { percent: Some(pct) });
        }
        return None;
    }
    None
}

fn parse_nuke(tokens: &[&str]) -> Option<TradeCommand> {
    if tokens.len() != 2 {
        return None;
    }
    match tokens[1].to_lowercase().as_str() {
        "orders" => Some(TradeCommand::NukeOrders),
        "positions" => Some(TradeCommand::NukePositions),
        "all" => Some(TradeCommand::NukeAll),
        _ => None,
    }
}

fn parse_size_spec(s: &str) -> Option<SizeSpec> {
    if let Some(rest) = s.strip_suffix('$') {
        let val = Decimal::from_str(rest).ok()?;
        Some(SizeSpec::Dollars(val))
    } else if let Some(rest) = s.strip_suffix('%') {
        let val = Decimal::from_str(rest).ok()?;
        Some(SizeSpec::Percent(val))
    } else {
        let val = Decimal::from_str(s).ok()?;
        Some(SizeSpec::Units(val))
    }
}

fn parse_price_spec(s: &str) -> Option<PriceSpec> {
    let lower = s.to_lowercase();
    if lower == "best" {
        return Some(PriceSpec::Best);
    }

    // Check for percentage offset: +1%, -2%
    if let Some(inner) = s.strip_suffix('%') {
        if inner.starts_with('+') || inner.starts_with('-') {
            let val = Decimal::from_str(inner).ok()?;
            return Some(PriceSpec::PercentOffset(val));
        }
        return None;
    }

    // Check for absolute offset: +2, -5
    if s.starts_with('+') || s.starts_with('-') {
        let val = Decimal::from_str(s).ok()?;
        return Some(PriceSpec::Offset(val));
    }

    // Absolute price
    let val = Decimal::from_str(s).ok()?;
    Some(PriceSpec::Absolute(val))
}

// ── Resolved types ──────────────────────────────────────────────────

#[allow(dead_code)]
pub struct ResolvedOrder {
    pub payload: ExecuteOrderPayload,
    pub description: String,
}

pub enum ResolvedAction {
    PlaceOrder(ResolvedOrder),
    ClosePosition(ResolvedOrder),
    NukeOrders,
    NukePositions,
    NukeAll,
}

// ── Resolver ────────────────────────────────────────────────────────

pub fn resolve_trade_command(
    cmd: &TradeCommand,
    symbol: &str,
    mark_price: Option<Decimal>,
    best_bid: Option<Decimal>,
    best_ask: Option<Decimal>,
    position: Option<(Side, Decimal)>, // (side, abs_quantity)
    max_order_qty: Option<Decimal>,
) -> Result<ResolvedAction, String> {
    match cmd {
        TradeCommand::Order {
            side,
            size,
            price,
            stop: _,
            reduce_only,
        } => {
            let quantity = resolve_size(size, mark_price, max_order_qty)?;
            let (order_type, resolved_price) =
                resolve_price(price, mark_price, best_bid, best_ask, side)?;

            let side_str = if *side == Side::Bid { "BUY" } else { "SELL" };
            let type_str = if order_type == OrderType::Market {
                "Market"
            } else {
                "Limit"
            };
            let price_info = resolved_price
                .map(|p| format!(" @ {p}"))
                .unwrap_or_default();
            let reduce_str = if *reduce_only { " reduce-only" } else { "" };
            let description =
                format!("{type_str} {side_str} {quantity} {symbol}{price_info}{reduce_str}");

            let payload = ExecuteOrderPayload {
                symbol: symbol.to_string(),
                side: *side,
                order_type,
                time_in_force: Some(if order_type == OrderType::Market {
                    TimeInForce::IOC
                } else {
                    TimeInForce::GTC
                }),
                quantity: Some(quantity),
                price: resolved_price,
                reduce_only: if *reduce_only { Some(true) } else { None },
                ..Default::default()
            };

            Ok(ResolvedAction::PlaceOrder(ResolvedOrder {
                payload,
                description,
            }))
        }

        TradeCommand::Close { percent } => {
            let (pos_side, pos_qty) =
                position.ok_or_else(|| format!("No open position on {symbol}"))?;

            let close_qty = match percent {
                Some(pct) => pos_qty * pct / Decimal::from(100),
                None => pos_qty,
            };

            // Close side is opposite of position side
            let close_side = if pos_side == Side::Bid {
                Side::Ask
            } else {
                Side::Bid
            };

            let pos_side_str = if pos_side == Side::Bid {
                "LONG"
            } else {
                "SHORT"
            };
            let description = match percent {
                Some(pct) => {
                    format!("Close {pct}% ({close_qty}) {pos_side_str} {symbol}")
                }
                None => format!("Close {pos_side_str} {pos_qty} {symbol}"),
            };

            let payload = ExecuteOrderPayload {
                symbol: symbol.to_string(),
                side: close_side,
                order_type: OrderType::Market,
                time_in_force: Some(TimeInForce::IOC),
                quantity: Some(close_qty),
                reduce_only: Some(true),
                ..Default::default()
            };

            Ok(ResolvedAction::ClosePosition(ResolvedOrder {
                payload,
                description,
            }))
        }

        TradeCommand::NukeOrders => Ok(ResolvedAction::NukeOrders),
        TradeCommand::NukePositions => Ok(ResolvedAction::NukePositions),
        TradeCommand::NukeAll => Ok(ResolvedAction::NukeAll),
    }
}

fn resolve_size(
    size: &SizeSpec,
    mark_price: Option<Decimal>,
    max_order_qty: Option<Decimal>,
) -> Result<Decimal, String> {
    match size {
        SizeSpec::Units(qty) => Ok(*qty),
        SizeSpec::Dollars(dollars) => {
            let price = mark_price.ok_or("No mark price available for $ size")?;
            if price.is_zero() {
                return Err("Mark price is zero".to_string());
            }
            Ok(*dollars / price)
        }
        SizeSpec::Percent(pct) => {
            let max = max_order_qty.ok_or("Could not determine max order quantity")?;
            Ok(max * *pct / Decimal::from(100))
        }
    }
}

fn resolve_price(
    spec: &Option<PriceSpec>,
    mark_price: Option<Decimal>,
    best_bid: Option<Decimal>,
    best_ask: Option<Decimal>,
    side: &Side,
) -> Result<(OrderType, Option<Decimal>), String> {
    match spec {
        None => Ok((OrderType::Market, None)),
        Some(PriceSpec::Absolute(p)) => Ok((OrderType::Limit, Some(*p))),
        Some(PriceSpec::Offset(offset)) => {
            let mark = mark_price.ok_or("No mark price available for offset price")?;
            Ok((OrderType::Limit, Some(mark + offset)))
        }
        Some(PriceSpec::PercentOffset(pct)) => {
            let mark = mark_price.ok_or("No mark price available for % offset price")?;
            let price = mark * (Decimal::ONE + *pct / Decimal::from(100));
            Ok((OrderType::Limit, Some(price)))
        }
        Some(PriceSpec::Best) => {
            let price = if *side == Side::Bid {
                best_bid.ok_or("No best bid available")?
            } else {
                best_ask.ok_or("No best ask available")?
            };
            Ok((OrderType::Limit, Some(price)))
        }
    }
}

// ── Tests ───────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_buy_units() {
        let cmd = parse_trade_command("buy 10").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::from(10)),
                price: None,
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_sell_at_price() {
        let cmd = parse_trade_command("sell 0.5 at 180").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Ask,
                size: SizeSpec::Units(Decimal::from_str("0.5").unwrap()),
                price: Some(PriceSpec::Absolute(Decimal::from(180))),
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_buy_dollars() {
        let cmd = parse_trade_command("buy 1000$").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Dollars(Decimal::from(1000)),
                price: None,
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_sell_percent_reduce() {
        let cmd = parse_trade_command("sell 25% reduce").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Ask,
                size: SizeSpec::Percent(Decimal::from(25)),
                price: None,
                stop: None,
                reduce_only: true,
            }
        );
    }

    #[test]
    fn test_buy_at_best() {
        let cmd = parse_trade_command("buy 1 at best").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::ONE),
                price: Some(PriceSpec::Best),
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_buy_best_shorthand() {
        let cmd = parse_trade_command("buy 1 best").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::ONE),
                price: Some(PriceSpec::Best),
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_buy_at_offset() {
        let cmd = parse_trade_command("buy 1 at +2").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::ONE),
                price: Some(PriceSpec::Offset(Decimal::from(2))),
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_buy_at_negative_percent() {
        let cmd = parse_trade_command("buy 1 at -1%").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::ONE),
                price: Some(PriceSpec::PercentOffset(Decimal::from(-1))),
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_buy_with_stop() {
        let cmd = parse_trade_command("buy 1 at 175 stop 170").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::ONE),
                price: Some(PriceSpec::Absolute(Decimal::from(175))),
                stop: Some(PriceSpec::Absolute(Decimal::from(170))),
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_close_full() {
        let cmd = parse_trade_command("close").unwrap();
        assert_eq!(cmd, TradeCommand::Close { percent: None });
    }

    #[test]
    fn test_close_partial() {
        let cmd = parse_trade_command("close 50%").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Close {
                percent: Some(Decimal::from(50)),
            }
        );
    }

    #[test]
    fn test_nuke_orders() {
        assert_eq!(
            parse_trade_command("nuke orders").unwrap(),
            TradeCommand::NukeOrders
        );
    }

    #[test]
    fn test_nuke_positions() {
        assert_eq!(
            parse_trade_command("nuke positions").unwrap(),
            TradeCommand::NukePositions
        );
    }

    #[test]
    fn test_nuke_all() {
        assert_eq!(
            parse_trade_command("nuke all").unwrap(),
            TradeCommand::NukeAll
        );
    }

    #[test]
    fn test_flatten() {
        assert_eq!(
            parse_trade_command("flatten").unwrap(),
            TradeCommand::NukeAll
        );
    }

    #[test]
    fn test_missing_size() {
        assert!(parse_trade_command("buy").is_none());
    }

    #[test]
    fn test_invalid_price() {
        assert!(parse_trade_command("buy 1 at bogus").is_none());
    }

    #[test]
    fn test_unrecognized_command() {
        assert!(parse_trade_command("foobar").is_none());
    }

    #[test]
    fn test_case_insensitive() {
        let cmd = parse_trade_command("BUY 10 AT 100").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::from(10)),
                price: Some(PriceSpec::Absolute(Decimal::from(100))),
                stop: None,
                reduce_only: false,
            }
        );
    }

    // ── Parser edge cases ──────────────────────────────────────────

    #[test]
    fn test_extra_whitespace_between_tokens() {
        let cmd = parse_trade_command("buy  10  at  175").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::from(10)),
                price: Some(PriceSpec::Absolute(Decimal::from(175))),
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_mixed_case_buy_at_best() {
        let cmd = parse_trade_command("BUY 10 At Best").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::from(10)),
                price: Some(PriceSpec::Best),
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_mixed_case_sell_reduce() {
        let cmd = parse_trade_command("Sell 5 REDUCE").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Ask,
                size: SizeSpec::Units(Decimal::from(5)),
                price: None,
                stop: None,
                reduce_only: true,
            }
        );
    }

    #[test]
    fn test_small_decimal_quantity() {
        let cmd = parse_trade_command("buy 0.001").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::from_str("0.001").unwrap()),
                price: None,
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_very_small_decimal_quantity_with_price() {
        let cmd = parse_trade_command("buy 0.00001 at 0.5").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::from_str("0.00001").unwrap()),
                price: Some(PriceSpec::Absolute(Decimal::from_str("0.5").unwrap())),
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_large_unit_quantity() {
        let cmd = parse_trade_command("buy 1000000").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::from(1_000_000)),
                price: None,
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_large_dollar_quantity_with_price() {
        let cmd = parse_trade_command("sell 999999$ at 99999").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Ask,
                size: SizeSpec::Dollars(Decimal::from(999_999)),
                price: Some(PriceSpec::Absolute(Decimal::from(99_999))),
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_zero_units() {
        let cmd = parse_trade_command("buy 0").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::ZERO),
                price: None,
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_zero_dollars() {
        let cmd = parse_trade_command("buy 0$").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Dollars(Decimal::ZERO),
                price: None,
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_close_zero_percent() {
        let cmd = parse_trade_command("close 0%").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Close {
                percent: Some(Decimal::ZERO),
            }
        );
    }

    #[test]
    fn test_negative_size_fails() {
        // Decimal::from_str("-10") succeeds, but negative sizes parse as units;
        // the parser itself does not reject negative numbers — it produces a Units(-10).
        // However, "-10" starts with '-', so parse_size_spec tries Decimal::from_str("-10")
        // which succeeds. Let's confirm the parser behavior.
        // Actually: parse_size_spec checks for '$' suffix, '%' suffix, then plain Decimal.
        // "-10" has no suffix so it attempts Decimal::from_str("-10") which is valid.
        let cmd = parse_trade_command("buy -10");
        // The parser doesn't reject negative sizes at the parse level.
        // "-10" is parsed as Units(-10).
        assert_eq!(
            cmd,
            Some(TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::from(-10)),
                price: None,
                stop: None,
                reduce_only: false,
            })
        );
    }

    #[test]
    fn test_multiple_modifiers_at_stop_reduce() {
        let cmd = parse_trade_command("buy 1 at 100 stop 90 reduce").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::ONE),
                price: Some(PriceSpec::Absolute(Decimal::from(100))),
                stop: Some(PriceSpec::Absolute(Decimal::from(90))),
                reduce_only: true,
            }
        );
    }

    #[test]
    fn test_invalid_command_buyy() {
        assert!(parse_trade_command("buyy 10").is_none());
    }

    #[test]
    fn test_buy_no_size() {
        assert!(parse_trade_command("buy").is_none());
    }

    #[test]
    fn test_sell_no_size() {
        assert!(parse_trade_command("sell").is_none());
    }

    #[test]
    fn test_nuke_alone_fails() {
        assert!(parse_trade_command("nuke").is_none());
    }

    #[test]
    fn test_nuke_unknown_target() {
        assert!(parse_trade_command("nuke foo").is_none());
    }

    #[test]
    fn test_close_non_percent_arg_fails() {
        assert!(parse_trade_command("close abc").is_none());
    }

    #[test]
    fn test_buy_at_missing_price_fails() {
        assert!(parse_trade_command("buy 10 at").is_none());
    }

    #[test]
    fn test_buy_stop_missing_price_fails() {
        assert!(parse_trade_command("buy 10 stop").is_none());
    }

    #[test]
    fn test_empty_string() {
        assert!(parse_trade_command("").is_none());
    }

    #[test]
    fn test_just_whitespace() {
        assert!(parse_trade_command("   ").is_none());
    }

    #[test]
    fn test_price_spec_plus_zero() {
        let cmd = parse_trade_command("buy 1 at +0").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::ONE),
                price: Some(PriceSpec::Offset(Decimal::ZERO)),
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_price_spec_minus_zero() {
        let cmd = parse_trade_command("buy 1 at -0").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::ONE),
                price: Some(PriceSpec::Offset(Decimal::ZERO)),
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_price_spec_plus_zero_percent() {
        let cmd = parse_trade_command("buy 1 at +0%").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::ONE),
                price: Some(PriceSpec::PercentOffset(Decimal::ZERO)),
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_price_spec_absolute_zero() {
        let cmd = parse_trade_command("buy 1 at 0").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::ONE),
                price: Some(PriceSpec::Absolute(Decimal::ZERO)),
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_buy_100_percent() {
        let cmd = parse_trade_command("buy 100%").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Percent(Decimal::from(100)),
                price: None,
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_buy_fractional_percent() {
        let cmd = parse_trade_command("buy 0.5%").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Percent(Decimal::from_str("0.5").unwrap()),
                price: None,
                stop: None,
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_close_100_percent() {
        let cmd = parse_trade_command("close 100%").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Close {
                percent: Some(Decimal::from(100)),
            }
        );
    }

    #[test]
    fn test_stop_with_negative_offset() {
        let cmd = parse_trade_command("buy 1 at 100 stop -5").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::ONE),
                price: Some(PriceSpec::Absolute(Decimal::from(100))),
                stop: Some(PriceSpec::Offset(Decimal::from(-5))),
                reduce_only: false,
            }
        );
    }

    #[test]
    fn test_stop_with_positive_percent_offset() {
        let cmd = parse_trade_command("buy 1 at 100 stop +2%").unwrap();
        assert_eq!(
            cmd,
            TradeCommand::Order {
                side: Side::Bid,
                size: SizeSpec::Units(Decimal::ONE),
                price: Some(PriceSpec::Absolute(Decimal::from(100))),
                stop: Some(PriceSpec::PercentOffset(Decimal::from(2))),
                reduce_only: false,
            }
        );
    }

    // ── Resolver edge cases ────────────────────────────────────────

    #[test]
    fn test_resolve_dollars_no_mark_price() {
        let cmd = TradeCommand::Order {
            side: Side::Bid,
            size: SizeSpec::Dollars(Decimal::from(1000)),
            price: None,
            stop: None,
            reduce_only: false,
        };
        let result = resolve_trade_command(&cmd, "SOL_USDC", None, None, None, None, None);
        match result {
            Err(e) => assert!(e.contains("No mark price"), "unexpected error: {e}"),
            Ok(_) => panic!("Expected error for dollars with no mark price"),
        }
    }

    #[test]
    fn test_resolve_dollars_zero_mark_price() {
        let cmd = TradeCommand::Order {
            side: Side::Bid,
            size: SizeSpec::Dollars(Decimal::from(1000)),
            price: None,
            stop: None,
            reduce_only: false,
        };
        let result = resolve_trade_command(
            &cmd,
            "SOL_USDC",
            Some(Decimal::ZERO),
            None,
            None,
            None,
            None,
        );
        match result {
            Err(e) => assert!(e.contains("Mark price is zero"), "unexpected error: {e}"),
            Ok(_) => panic!("Expected error for dollars with zero mark price"),
        }
    }

    #[test]
    fn test_resolve_percent_no_max_order_qty() {
        let cmd = TradeCommand::Order {
            side: Side::Bid,
            size: SizeSpec::Percent(Decimal::from(50)),
            price: None,
            stop: None,
            reduce_only: false,
        };
        let result = resolve_trade_command(&cmd, "SOL_USDC", None, None, None, None, None);
        match result {
            Err(e) => assert!(e.contains("max order quantity"), "unexpected error: {e}"),
            Ok(_) => panic!("Expected error for percent with no max order qty"),
        }
    }

    #[test]
    fn test_resolve_close_no_position() {
        let cmd = TradeCommand::Close { percent: None };
        let result = resolve_trade_command(&cmd, "SOL_USDC", None, None, None, None, None);
        match result {
            Err(e) => assert!(e.contains("No open position"), "unexpected error: {e}"),
            Ok(_) => panic!("Expected error for close with no position"),
        }
    }

    #[test]
    fn test_resolve_close_short_position() {
        let cmd = TradeCommand::Close { percent: None };
        let result = resolve_trade_command(
            &cmd,
            "SOL_USDC",
            None,
            None,
            None,
            Some((Side::Ask, Decimal::from(5))),
            None,
        );
        let action = result.unwrap();
        match action {
            ResolvedAction::ClosePosition(order) => {
                assert_eq!(order.payload.side, Side::Bid);
                assert_eq!(order.payload.quantity, Some(Decimal::from(5)));
                assert_eq!(order.payload.reduce_only, Some(true));
                assert!(order.description.contains("SHORT"));
            }
            _ => panic!("Expected ClosePosition"),
        }
    }

    #[test]
    fn test_resolve_close_long_position() {
        let cmd = TradeCommand::Close { percent: None };
        let result = resolve_trade_command(
            &cmd,
            "SOL_USDC",
            None,
            None,
            None,
            Some((Side::Bid, Decimal::from(10))),
            None,
        );
        let action = result.unwrap();
        match action {
            ResolvedAction::ClosePosition(order) => {
                assert_eq!(order.payload.side, Side::Ask);
                assert_eq!(order.payload.quantity, Some(Decimal::from(10)));
                assert!(order.description.contains("LONG"));
            }
            _ => panic!("Expected ClosePosition"),
        }
    }

    #[test]
    fn test_resolve_best_on_buy_uses_best_bid() {
        let cmd = TradeCommand::Order {
            side: Side::Bid,
            size: SizeSpec::Units(Decimal::ONE),
            price: Some(PriceSpec::Best),
            stop: None,
            reduce_only: false,
        };
        let result = resolve_trade_command(
            &cmd,
            "SOL_USDC",
            None,
            Some(Decimal::from(170)),
            Some(Decimal::from(171)),
            None,
            None,
        );
        let action = result.unwrap();
        match action {
            ResolvedAction::PlaceOrder(order) => {
                assert_eq!(order.payload.price, Some(Decimal::from(170)));
                assert_eq!(order.payload.order_type, OrderType::Limit);
            }
            _ => panic!("Expected PlaceOrder"),
        }
    }

    #[test]
    fn test_resolve_best_on_sell_uses_best_ask() {
        let cmd = TradeCommand::Order {
            side: Side::Ask,
            size: SizeSpec::Units(Decimal::ONE),
            price: Some(PriceSpec::Best),
            stop: None,
            reduce_only: false,
        };
        let result = resolve_trade_command(
            &cmd,
            "SOL_USDC",
            None,
            Some(Decimal::from(170)),
            Some(Decimal::from(171)),
            None,
            None,
        );
        let action = result.unwrap();
        match action {
            ResolvedAction::PlaceOrder(order) => {
                assert_eq!(order.payload.price, Some(Decimal::from(171)));
                assert_eq!(order.payload.order_type, OrderType::Limit);
            }
            _ => panic!("Expected PlaceOrder"),
        }
    }

    #[test]
    fn test_resolve_best_buy_no_bid_errors() {
        let cmd = TradeCommand::Order {
            side: Side::Bid,
            size: SizeSpec::Units(Decimal::ONE),
            price: Some(PriceSpec::Best),
            stop: None,
            reduce_only: false,
        };
        let result = resolve_trade_command(&cmd, "SOL_USDC", None, None, None, None, None);
        match result {
            Err(e) => assert!(e.contains("No best bid"), "unexpected error: {e}"),
            Ok(_) => panic!("Expected error for best buy with no bid"),
        }
    }

    #[test]
    fn test_resolve_best_sell_no_ask_errors() {
        let cmd = TradeCommand::Order {
            side: Side::Ask,
            size: SizeSpec::Units(Decimal::ONE),
            price: Some(PriceSpec::Best),
            stop: None,
            reduce_only: false,
        };
        let result = resolve_trade_command(&cmd, "SOL_USDC", None, None, None, None, None);
        match result {
            Err(e) => assert!(e.contains("No best ask"), "unexpected error: {e}"),
            Ok(_) => panic!("Expected error for best sell with no ask"),
        }
    }

    #[test]
    fn test_resolve_offset_no_mark_price_errors() {
        let cmd = TradeCommand::Order {
            side: Side::Bid,
            size: SizeSpec::Units(Decimal::ONE),
            price: Some(PriceSpec::Offset(Decimal::from(5))),
            stop: None,
            reduce_only: false,
        };
        let result = resolve_trade_command(&cmd, "SOL_USDC", None, None, None, None, None);
        match result {
            Err(e) => assert!(e.contains("No mark price"), "unexpected error: {e}"),
            Ok(_) => panic!("Expected error for offset with no mark price"),
        }
    }

    #[test]
    fn test_resolve_percent_offset_no_mark_price_errors() {
        let cmd = TradeCommand::Order {
            side: Side::Bid,
            size: SizeSpec::Units(Decimal::ONE),
            price: Some(PriceSpec::PercentOffset(Decimal::from(2))),
            stop: None,
            reduce_only: false,
        };
        let result = resolve_trade_command(&cmd, "SOL_USDC", None, None, None, None, None);
        match result {
            Err(e) => assert!(e.contains("No mark price"), "unexpected error: {e}"),
            Ok(_) => panic!("Expected error for percent offset with no mark price"),
        }
    }

    #[test]
    fn test_resolve_nuke_orders() {
        let cmd = TradeCommand::NukeOrders;
        let result = resolve_trade_command(&cmd, "SOL_USDC", None, None, None, None, None);
        assert!(matches!(result.unwrap(), ResolvedAction::NukeOrders));
    }

    #[test]
    fn test_resolve_nuke_positions() {
        let cmd = TradeCommand::NukePositions;
        let result = resolve_trade_command(&cmd, "SOL_USDC", None, None, None, None, None);
        assert!(matches!(result.unwrap(), ResolvedAction::NukePositions));
    }

    #[test]
    fn test_resolve_nuke_all() {
        let cmd = TradeCommand::NukeAll;
        let result = resolve_trade_command(&cmd, "SOL_USDC", None, None, None, None, None);
        assert!(matches!(result.unwrap(), ResolvedAction::NukeAll));
    }

    #[test]
    fn test_resolve_market_order_has_ioc_tif() {
        let cmd = TradeCommand::Order {
            side: Side::Bid,
            size: SizeSpec::Units(Decimal::from(10)),
            price: None,
            stop: None,
            reduce_only: false,
        };
        let result = resolve_trade_command(&cmd, "SOL_USDC", None, None, None, None, None);
        match result.unwrap() {
            ResolvedAction::PlaceOrder(order) => {
                assert_eq!(order.payload.order_type, OrderType::Market);
                assert_eq!(order.payload.time_in_force, Some(TimeInForce::IOC));
            }
            _ => panic!("Expected PlaceOrder"),
        }
    }

    #[test]
    fn test_resolve_limit_order_has_gtc_tif() {
        let cmd = TradeCommand::Order {
            side: Side::Bid,
            size: SizeSpec::Units(Decimal::ONE),
            price: Some(PriceSpec::Absolute(Decimal::from(100))),
            stop: None,
            reduce_only: false,
        };
        let result = resolve_trade_command(&cmd, "SOL_USDC", None, None, None, None, None);
        match result.unwrap() {
            ResolvedAction::PlaceOrder(order) => {
                assert_eq!(order.payload.order_type, OrderType::Limit);
                assert_eq!(order.payload.time_in_force, Some(TimeInForce::GTC));
                assert_eq!(order.payload.price, Some(Decimal::from(100)));
            }
            _ => panic!("Expected PlaceOrder"),
        }
    }

    #[test]
    fn test_resolve_reduce_only_flag() {
        let cmd = TradeCommand::Order {
            side: Side::Ask,
            size: SizeSpec::Units(Decimal::from(5)),
            price: None,
            stop: None,
            reduce_only: true,
        };
        let result = resolve_trade_command(&cmd, "SOL_USDC", None, None, None, None, None);
        match result.unwrap() {
            ResolvedAction::PlaceOrder(order) => {
                assert_eq!(order.payload.reduce_only, Some(true));
                assert!(order.description.contains("reduce-only"));
            }
            _ => panic!("Expected PlaceOrder"),
        }
    }

    #[test]
    fn test_resolve_dollars_correct_quantity() {
        let cmd = TradeCommand::Order {
            side: Side::Bid,
            size: SizeSpec::Dollars(Decimal::from(500)),
            price: None,
            stop: None,
            reduce_only: false,
        };
        // mark_price = 100, so 500$ / 100 = 5 units
        let result = resolve_trade_command(
            &cmd,
            "SOL_USDC",
            Some(Decimal::from(100)),
            None,
            None,
            None,
            None,
        );
        match result.unwrap() {
            ResolvedAction::PlaceOrder(order) => {
                assert_eq!(order.payload.quantity, Some(Decimal::from(5)));
            }
            _ => panic!("Expected PlaceOrder"),
        }
    }

    #[test]
    fn test_resolve_percent_correct_quantity() {
        let cmd = TradeCommand::Order {
            side: Side::Bid,
            size: SizeSpec::Percent(Decimal::from(50)),
            price: None,
            stop: None,
            reduce_only: false,
        };
        // max_order_qty = 200, so 50% of 200 = 100
        let result = resolve_trade_command(
            &cmd,
            "SOL_USDC",
            None,
            None,
            None,
            None,
            Some(Decimal::from(200)),
        );
        match result.unwrap() {
            ResolvedAction::PlaceOrder(order) => {
                assert_eq!(order.payload.quantity, Some(Decimal::from(100)));
            }
            _ => panic!("Expected PlaceOrder"),
        }
    }

    #[test]
    fn test_resolve_close_partial_percent() {
        let cmd = TradeCommand::Close {
            percent: Some(Decimal::from(50)),
        };
        let result = resolve_trade_command(
            &cmd,
            "SOL_USDC",
            None,
            None,
            None,
            Some((Side::Bid, Decimal::from(10))),
            None,
        );
        match result.unwrap() {
            ResolvedAction::ClosePosition(order) => {
                assert_eq!(order.payload.quantity, Some(Decimal::from(5)));
                assert_eq!(order.payload.side, Side::Ask);
                assert!(order.description.contains("50%"));
            }
            _ => panic!("Expected ClosePosition"),
        }
    }

    #[test]
    fn test_resolve_offset_computes_correctly() {
        let cmd = TradeCommand::Order {
            side: Side::Bid,
            size: SizeSpec::Units(Decimal::ONE),
            price: Some(PriceSpec::Offset(Decimal::from(-5))),
            stop: None,
            reduce_only: false,
        };
        // mark_price = 100, offset = -5, so limit price = 95
        let result = resolve_trade_command(
            &cmd,
            "SOL_USDC",
            Some(Decimal::from(100)),
            None,
            None,
            None,
            None,
        );
        match result.unwrap() {
            ResolvedAction::PlaceOrder(order) => {
                assert_eq!(order.payload.price, Some(Decimal::from(95)));
                assert_eq!(order.payload.order_type, OrderType::Limit);
            }
            _ => panic!("Expected PlaceOrder"),
        }
    }

    #[test]
    fn test_resolve_percent_offset_computes_correctly() {
        let cmd = TradeCommand::Order {
            side: Side::Bid,
            size: SizeSpec::Units(Decimal::ONE),
            price: Some(PriceSpec::PercentOffset(Decimal::from(-10))),
            stop: None,
            reduce_only: false,
        };
        // mark_price = 200, -10% => 200 * (1 + (-10/100)) = 200 * 0.9 = 180
        let result = resolve_trade_command(
            &cmd,
            "SOL_USDC",
            Some(Decimal::from(200)),
            None,
            None,
            None,
            None,
        );
        match result.unwrap() {
            ResolvedAction::PlaceOrder(order) => {
                assert_eq!(order.payload.price, Some(Decimal::from(180)));
            }
            _ => panic!("Expected PlaceOrder"),
        }
    }
}
