/// Synchronous fill simulation for the swym backtest engine.
///
/// Implements correct two-sided spot balance accounting matching Binance spot fee rules:
/// - Buy:  spend quote (USDC) + fees in quote; receive base (BTC) at full quantity
/// - Sell: spend base (BTC) at full quantity; receive quote (USDC) minus fees in quote
///
/// This differs from barter's `MockExchange::open_order()` which deducts both sides from the
/// quote asset (a quirk that produces incorrect results for backtesting purposes).
use barter::engine::state::{asset::AssetStates, instrument::InstrumentState};
use barter_execution::{
    balance::{AssetBalance, Balance},
    order::{id::OrderId, request::OrderRequestOpen},
    trade::{AssetFees, Trade, TradeId},
};
use barter_instrument::{
    Side,
    asset::{AssetIndex, QuoteAsset},
    exchange::ExchangeIndex,
    instrument::InstrumentIndex,
};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;

/// Result of simulating a market fill.
pub struct FillResult {
    /// The filled trade (timestamped to the current market event time).
    pub trade: Trade<QuoteAsset, InstrumentIndex>,
    /// Updated balance for the asset that was spent (quote on buy, base on sell).
    pub spent_balance: AssetBalance<AssetIndex>,
    /// Updated balance for the asset that was received (base on buy, quote on sell).
    pub received_balance: AssetBalance<AssetIndex>,
}

/// Simulate an immediate fill for a market order.
///
/// Returns `None` if the required asset balance is insufficient.
pub fn simulate_market_fill<InstrumentData>(
    request: &OrderRequestOpen<ExchangeIndex, InstrumentIndex>,
    instrument_state: &InstrumentState<InstrumentData>,
    assets: &AssetStates,
    fees_percent: Decimal,
    time_exchange: DateTime<Utc>,
    order_sequence: &mut u64,
) -> Option<FillResult> {
    let side = request.state.side;
    let price = request.state.price;
    let quantity = request.state.quantity.abs();
    let base_asset_index = instrument_state.instrument.underlying.base;
    let quote_asset_index = instrument_state.instrument.underlying.quote;

    let get_balance = |idx: &AssetIndex| {
        assets
            .asset_index(idx)
            .balance
            .as_ref()
            .map(|t| t.value)
            .unwrap_or(Balance { total: Decimal::ZERO, free: Decimal::ZERO })
    };

    let (spent_asset, received_asset, spent_balance, received_balance, fees) = match side {
        Side::Buy => {
            // Spend quote, receive base
            let order_value_quote = price * quantity;
            let fees_quote = order_value_quote * fees_percent;
            let total_quote_required = order_value_quote + fees_quote;

            let current_quote = get_balance(&quote_asset_index);
            let new_quote_free = current_quote.free - total_quote_required;
            if new_quote_free < Decimal::ZERO {
                return None;
            }

            let current_base = get_balance(&base_asset_index);
            let new_base_free = current_base.free + quantity;

            (
                quote_asset_index,
                base_asset_index,
                Balance { total: new_quote_free, free: new_quote_free },
                Balance { total: new_base_free, free: new_base_free },
                AssetFees::quote_fees(fees_quote),
            )
        }
        Side::Sell => {
            // Spend base, receive quote minus fees.
            // Fees are taken from quote proceeds (matching Binance spot behaviour),
            // so the full `quantity` of base is spent and the fee is not deducted from base.
            let current_base = get_balance(&base_asset_index);
            let new_base_free = current_base.free - quantity;
            if new_base_free < Decimal::ZERO {
                return None;
            }

            let proceeds_quote = price * quantity;
            let fees_quote = proceeds_quote * fees_percent;
            let net_quote = proceeds_quote - fees_quote;

            let current_quote = get_balance(&quote_asset_index);
            let new_quote_free = current_quote.free + net_quote;

            (
                base_asset_index,
                quote_asset_index,
                Balance { total: new_base_free, free: new_base_free },
                Balance { total: new_quote_free, free: new_quote_free },
                AssetFees::quote_fees(fees_quote),
            )
        }
    };

    let seq = *order_sequence;
    *order_sequence += 1;
    let id_str = seq.to_string();

    let trade = Trade {
        id: TradeId::new(&id_str),
        order_id: OrderId::new(&id_str),
        instrument: request.key.instrument,
        strategy: request.key.strategy.clone(),
        time_exchange,
        side,
        price,
        quantity: request.state.quantity,
        fees,
    };

    Some(FillResult {
        trade,
        spent_balance: AssetBalance { asset: spent_asset, balance: spent_balance, time_exchange },
        received_balance: AssetBalance {
            asset: received_asset,
            balance: received_balance,
            time_exchange,
        },
    })
}
