use std::{
    cmp::{max, min},
    fmt::Display,
    ops::Mul,
};
use alloy_primitives::FixedBytes;
use itertools::Itertools;
use malachite::{
    num::basic::traits::{One, Two, Zero},
    Rational,
};
use super::utils::calculate_weight;
const R2: Rational = Rational::TWO;
use super::{config::CexDexTradeConfig, time_window_vwam::ExchangePath};
use crate::{
    constants::{USDC_ADDRESS, USDT_ADDRESS},
    db::cex::{
        trades::{
            utils::{log_insufficient_trade_volume, log_missing_trade_data, TimeBasketQueue},
            CexTrades, Direction, SortedTrades,
        },
        CexExchange,
    },
    display::utils::format_etherscan_url,
    mev::OptimisticTrade,
    normalized_actions::NormalizedSwap,
    pair::Pair,
    utils::ToFloatNearest,
    FastHashMap,
};
pub const BASE_EXECUTION_QUALITY: usize = 80;
#[derive(Debug, Clone)]
pub struct OptimisticPrice {
    pub trades_used: Vec<OptimisticTrade>,
    pub pairs:       Vec<Pair>,
    pub global:      ExchangePath,
}
impl Mul for OptimisticPrice {
    type Output = OptimisticPrice;
    fn mul(mut self, rhs: Self) -> Self::Output {
        self.pairs.extend(rhs.pairs);
        self.global.price_maker *= rhs.global.price_maker;
        self.global.price_taker *= rhs.global.price_taker;
        self.global.final_start_time =
            min(self.global.final_start_time, rhs.global.final_start_time);
        self.global.final_end_time = max(self.global.final_end_time, rhs.global.final_end_time);
        self.trades_used.extend(rhs.trades_used);
        self
    }
}
impl Display for OptimisticPrice {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "{:#?}", self.trades_used)?;
        writeln!(f, "{}", self.global.price_maker.clone().to_float())?;
        writeln!(f, "{}", self.global.price_taker.clone().to_float())?;
        Ok(())
    }
}
impl<'a> SortedTrades<'a> {
    pub(crate) fn get_optimistic_price(
        &mut self,
        config: CexDexTradeConfig,
        _exchanges: &[CexExchange],
        block_timestamp: u64,
        pair: Pair,
        volume: &Rational,
        quality: Option<FastHashMap<CexExchange, FastHashMap<Pair, usize>>>,
        bypass_vol: bool,
        dex_swap: &NormalizedSwap,
        tx_hash: FixedBytes<32>,
    ) -> Option<OptimisticPrice> {
        if pair.0 == pair.1 {
            return Some(OptimisticPrice {
                trades_used: vec![],
                pairs:       vec![pair],
                global:      ExchangePath {
                    price_maker:      Rational::ONE,
                    price_taker:      Rational::ONE,
                    volume:           Rational::ZERO,
                    final_start_time: 0,
                    final_end_time:   0,
                    was_intermediary: false,
                },
            })
        }
        let res = self
            .get_optimistic_direct(
                config,
                block_timestamp,
                pair,
                volume,
                bypass_vol,
                quality.as_ref(),
                dex_swap,
                tx_hash,
                false,
            )
            .or_else(|| {
                self.get_optimistic_via_intermediary(
                    config,
                    block_timestamp,
                    pair,
                    volume,
                    bypass_vol,
                    quality.as_ref(),
                    dex_swap,
                    tx_hash,
                )
            });
        if res.is_none() {
            tracing::debug!(target: "brontes_types::db::cex::optimistic", ?pair, "No price VMAP found for {}-{} in optimistic time window. \n Tx: {}", dex_swap.token_in.symbol, dex_swap.token_out.symbol, format_etherscan_url(&tx_hash));
        }
        res
    }
    fn get_optimistic_via_intermediary(
        &self,
        config: CexDexTradeConfig,
        block_timestamp: u64,
        pair: Pair,
        volume: &Rational,
        bypass_vol: bool,
        quality: Option<&FastHashMap<CexExchange, FastHashMap<Pair, usize>>>,
        dex_swap: &NormalizedSwap,
        tx_hash: FixedBytes<32>,
    ) -> Option<OptimisticPrice> {
        self.calculate_intermediary_addresses(&pair)
            .into_iter()
            .filter_map(|intermediary| {
                let pair0 = Pair(pair.0, intermediary);
                let pair1 = Pair(intermediary, pair.1);
                tracing::debug!(target: "brontes_types::db::cex::trades::optimistic", ?pair, ?intermediary, "trying via intermediary");
                let mut bypass_intermediary_vol = false;
                if pair0.0 == USDC_ADDRESS && pair0.1 == USDT_ADDRESS
                || pair0.0 == USDT_ADDRESS && pair0.1 == USDC_ADDRESS {
                    bypass_intermediary_vol = true;
                }
                let first_leg = self.get_optimistic_direct(
                    config,
                    block_timestamp,
                    pair0,
                    volume,
                    bypass_vol || bypass_intermediary_vol,
                    quality,
                    dex_swap,
                    tx_hash,
                    true
                )?;
                let new_vol = volume
                    * ((&first_leg.global.price_maker + &first_leg.global.price_taker) / R2);
                bypass_intermediary_vol = false;
                if pair1.0 == USDT_ADDRESS && pair1.1 == USDC_ADDRESS
                || pair1.0 == USDC_ADDRESS && pair1.1 == USDT_ADDRESS{
                    bypass_intermediary_vol = true;
                }
                let second_leg = self.get_optimistic_direct(
                    config,
                    block_timestamp,
                    pair1,
                    &new_vol,
                    bypass_vol || bypass_intermediary_vol,
                    quality,
                    dex_swap,
                    tx_hash,
                    true
                )?;
                let price = first_leg * second_leg;
                Some(price)
            })
            .max_by_key(|a| a.global.price_maker.clone())
    }
    fn get_optimistic_direct(
        &self,
        config: CexDexTradeConfig,
        block_timestamp: u64,
        pair: Pair,
        volume: &Rational,
        bypass_vol: bool,
        quality: Option<&FastHashMap<CexExchange, FastHashMap<Pair, usize>>>,
        dex_swap: &NormalizedSwap,
        tx_hash: FixedBytes<32>,
        was_inter: bool,
    ) -> Option<OptimisticPrice> {
        let quality_pct = quality.map(|map| {
            map.iter()
                .map(|(k, v)| (*k, v.get(&pair).copied().unwrap_or(BASE_EXECUTION_QUALITY)))
                .collect::<FastHashMap<_, _>>()
        });
        let trade_data = self.get_trades(pair, dex_swap, tx_hash)?;
        let mut baskets_queue =
            TimeBasketQueue::new(trade_data, block_timestamp, quality_pct, &config);
        baskets_queue.construct_time_baskets();
        while baskets_queue.volume.lt(volume) {
            if baskets_queue.get_min_time_delta(block_timestamp)
                >= config.max_optimistic_pre_block_us
                || baskets_queue.get_max_time_delta(block_timestamp)
                    >= config.max_optimistic_post_block_us
            {
                break
            }
            let min_expand = (baskets_queue.get_max_time_delta(block_timestamp)
                >= config.optimistic_scaling_diff_us)
                .then_some(config.optimistic_time_step_us)
                .unwrap_or_default();
            baskets_queue.expand_time_bounds(min_expand, config.optimistic_time_step_us);
        }
        let mut trades_used: Vec<CexTrades> = Vec::new();
        let mut unfilled = Rational::ZERO;
        for basket in baskets_queue.baskets {
            let to_fill: Rational = ((&basket.volume / &baskets_queue.volume) * volume) + &unfilled;
            let (basket_trades, basket_unfilled) = basket.get_trades_used(&to_fill);
            unfilled = basket_unfilled;
            trades_used.extend(basket_trades);
        }
        let mut vxp_maker = Rational::ZERO;
        let mut vxp_taker = Rational::ZERO;
        let mut trade_volume = Rational::ZERO;
        let mut trade_volume_weight = Rational::ZERO;
        let mut optimistic_trades = Vec::with_capacity(trades_used.len());
        let mut global_start_time = u64::MAX;
        let mut global_end_time = 0;
        for trade in trades_used {
            let (m_fee, t_fee) = trade.exchange.fees();
            let weight = if config.use_block_time_weights_vwap {
                calculate_weight(
                    block_timestamp,
                    trade.timestamp,
                    config.pre_decay_weight_vwap,
                    config.post_decay_weight_op,
                )
            } else {
                Rational::ONE
            };
            vxp_maker += (&trade.price * (Rational::ONE - m_fee)) * &trade.amount * &weight;
            vxp_taker += (&trade.price * (Rational::ONE - t_fee)) * &trade.amount * &weight;
            trade_volume_weight += &trade.amount * &weight;
            trade_volume += &trade.amount;
            optimistic_trades.push(OptimisticTrade {
                volume: trade.amount.clone(),
                pair,
                price: trade.price.clone(),
                exchange: trade.exchange,
                timestamp: trade.timestamp,
            });
            global_start_time = min(global_start_time, trade.timestamp);
            global_end_time = max(global_end_time, trade.timestamp);
        }
        if global_start_time == u64::MAX {
            global_start_time = 0;
        }
        if &trade_volume < volume && !bypass_vol {
            log_insufficient_trade_volume(pair, dex_swap, &tx_hash, trade_volume, volume.clone());
            return None
        } else if trade_volume == Rational::ZERO {
            return None
        }
        let global = ExchangePath {
            price_maker:      vxp_maker / &trade_volume_weight,
            price_taker:      vxp_taker / &trade_volume_weight,
            volume:           trade_volume,
            final_start_time: global_start_time,
            final_end_time:   global_end_time,
            was_intermediary: was_inter,
        };
        let price = OptimisticPrice { trades_used: optimistic_trades, pairs: vec![pair], global };
        Some(price)
    }
    pub fn get_trades(
        &'a self,
        pair: Pair,
        dex_swap: &NormalizedSwap,
        tx_hash: FixedBytes<32>,
    ) -> Option<OptimisticTradeData> {
        if let Some((indices, trades)) = self.0.get(&pair) {
            let adjusted_trades = trades
                .iter()
                .map(|trade| trade.adjust_for_direction(Direction::Sell))
                .collect_vec();
            Some(OptimisticTradeData {
                indices:   *indices,
                trades:    adjusted_trades,
                direction: Direction::Sell,
            })
        } else {
            let flipped_pair = pair.flip();
            if let Some((indices, trades)) = self.0.get(&flipped_pair) {
                let adjusted_trades = trades
                    .iter()
                    .map(|trade| trade.adjust_for_direction(Direction::Buy))
                    .collect_vec();
                Some(OptimisticTradeData {
                    indices:   *indices,
                    trades:    adjusted_trades,
                    direction: Direction::Buy,
                })
            } else {
                log_missing_trade_data(dex_swap, &tx_hash);
                None
            }
        }
    }
}
pub struct OptimisticTradeData {
    pub indices:   (usize, usize),
    pub trades:    Vec<CexTrades>,
    pub direction: Direction,
}