use debot_utils::DynamicLimitedSizeMap;
use debot_utils::LimitedSizeMap;
use debot_utils::ValuePair;
use rust_decimal::Decimal;

use crate::trend_metrics::TrendType;
use crate::{
    trade_action::TradeDetail,
    trend_metrics::{CrossOverType, TrendMetrics},
    PricePoint, TradeAction,
};
use std::{cell::RefCell, collections::VecDeque};

#[derive(Debug, Clone, PartialEq)]
pub enum MarketCondition {
    UpBreak,
    DownBreak,
    UpInit,
    DownInit,
    HotMarket,
    ColdMarket,
    RangeBound,
    Unknown,
}

#[derive(Debug, Clone)]
struct MarketDataConfig {
    short_period: usize,
    long_period: usize,
    trading_period: usize,
    execution_period: usize,
    max_size: usize,
}

#[derive(Debug, Clone)]
struct MarketDataMetrics {
    price: RefCell<TrendMetrics>,
    rsi: RefCell<TrendMetrics>,
    adx: TrendMetrics,
    stochastic: TrendMetrics,
    atr: Option<Decimal>,
}

impl MarketDataMetrics {
    fn new(short_period: usize, long_period: usize, macd_period: usize) -> Self {
        Self {
            price: TrendMetrics::new(short_period, long_period, macd_period).into(),
            rsi: TrendMetrics::new(short_period, long_period, macd_period).into(),
            adx: TrendMetrics::new(short_period, long_period, macd_period),
            stochastic: TrendMetrics::new(short_period, long_period, macd_period),
            atr: None,
        }
    }
}

#[derive(Clone)]
pub struct MarketData {
    name: String,
    prices: VecDeque<PricePoint>,
    price_mean: Decimal,
    price_variance: Decimal,
    trade_period_counter: usize,
    config: MarketDataConfig,
    metrics: MarketDataMetrics,
    probability: TransactionProbability,
}

#[derive(Clone)]
struct TransactionProbability {
    max_entry_size: usize,
    fill_probability: LimitedSizeMap<Decimal, DynamicLimitedSizeMap<Decimal, ValuePair<u32>>>,
}

impl TransactionProbability {
    fn new(max_size: usize) -> Self {
        TransactionProbability {
            max_entry_size: max_size,
            fill_probability: LimitedSizeMap::new(max_size),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TradingStrategy {
    TrendFollow,
    Rebalance,
    MarketMake,
}

impl MarketData {
    pub fn new(
        name: String,
        short_period: usize,
        long_period: usize,
        trading_period: usize,
        execution_period: usize,
        max_size: usize,
    ) -> MarketData {
        let config = MarketDataConfig {
            short_period,
            long_period,
            trading_period,
            execution_period,
            max_size,
        };
        let metrics = MarketDataMetrics::new(short_period, long_period, trading_period);
        MarketData {
            name,
            prices: VecDeque::with_capacity(max_size),
            config,
            metrics,
            price_mean: Decimal::ZERO,
            price_variance: Decimal::ZERO,
            trade_period_counter: 0,
            probability: TransactionProbability::new(1024),
        }
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn last_price(&self) -> Decimal {
        self.metrics
            .price
            .borrow()
            .val
            .current_value()
            .unwrap_or_default()
    }

    pub fn atr(&self) -> Decimal {
        match self.metrics.atr {
            Some(v) => v,
            None => Decimal::ZERO,
        }
    }

    pub fn adx(&self) -> Decimal {
        match self.metrics.adx.val.current_ema() {
            Some(v) => v,
            None => Decimal::ZERO,
        }
    }

    pub fn rsi(&self) -> Decimal {
        match self.metrics.rsi.borrow().val.current_ema() {
            Some(v) => v,
            None => Decimal::new(5, 1),
        }
    }

    pub fn add_transaction_price(&mut self, trade_price: Decimal) {
        if self.prices.len() < self.config.trading_period {
            return;
        }
        let mid_price_index = self.prices.len() - self.config.trading_period;

        let mid_prices: Vec<Decimal> = self
            .prices
            .iter()
            .skip(mid_price_index)
            .map(|pp| pp.price)
            .collect();

        for mid_price in mid_prices {
            match self.probability.fill_probability.get_mut(&mid_price) {
                Some(value_pair_map) => {
                    let mut changes = Vec::new();

                    for (price, _value_pair) in value_pair_map.iter() {
                        let condition = if trade_price > mid_price {
                            mid_price <= *price && *price <= trade_price
                        } else {
                            trade_price <= *price && *price <= mid_price
                        };

                        if condition {
                            changes.push((*price, 1, 0));
                        } else {
                            changes.push((*price, 0, 1));
                        }
                    }

                    for (price, add1, add2) in changes {
                        value_pair_map.add(price, add1, add2);
                    }

                    if value_pair_map.get(&trade_price).is_none() {
                        value_pair_map.add(trade_price, 1, 0);
                    }
                }
                None => {
                    let mut value_pair_map = DynamicLimitedSizeMap::<Decimal, ValuePair<u32>>::new(
                        self.probability.max_entry_size,
                    );
                    value_pair_map.add(trade_price, 1, 0);
                    self.probability
                        .fill_probability
                        .insert(mid_price, value_pair_map);
                }
            }
        }
    }

    pub fn add_price(&mut self, price: Option<Decimal>, timestamp: Option<i64>) -> PricePoint {
        if self.prices.len() == self.config.max_size {
            self.prices.pop_front();
        }

        let price = match price {
            Some(price) => price,
            None => {
                if self.prices.len() == 0 {
                    Decimal::ZERO
                } else {
                    match self.prices.iter().last() {
                        Some(v) => v.price,
                        None => Decimal::ZERO,
                    }
                }
            }
        };

        let price_point = PricePoint::new(price, timestamp);

        self.prices.push_back(price_point.clone());

        self.update_mean_and_variance(self.config.trading_period);
        self.update_ema(price);
        self.update_rsi();
        self.update_adx();
        self.update_stochastic();
        self.update_atr();

        self.trade_period_counter += 1;
        if self.trade_period_counter > self.config.execution_period {
            self.trade_period_counter = 1;
        }

        price_point
    }

    pub fn is_open_signaled(
        &mut self,
        strategy: TradingStrategy,
        force_open: bool,
        min_tick: Decimal,
        mid_price: Decimal,
    ) -> Vec<TradeAction> {
        let mut actions: Vec<TradeAction> = vec![];

        let is_crossover = self.metrics.price.borrow_mut().is_crossover();

        match strategy {
            TradingStrategy::TrendFollow | TradingStrategy::Rebalance => {
                let confidence = Decimal::ONE;
                match is_crossover {
                    CrossOverType::GoldenCross => {
                        let action = TradeAction::BuyOpen(TradeDetail::new(None, None, confidence));
                        actions.push(action);
                    }
                    CrossOverType::DeadCross => {
                        let action =
                            TradeAction::SellOpen(TradeDetail::new(None, None, confidence));
                        actions.push(action);
                    }
                    _ => {}
                };
            }
            _ => {}
        }

        match strategy {
            TradingStrategy::MarketMake => {
                if self.trade_period_counter == self.config.execution_period || force_open {
                    // Order buy and sell actions
                    actions = self.get_market_make_actions(min_tick, mid_price);
                }
            }
            TradingStrategy::Rebalance => {
                if self.trade_period_counter == 1 || force_open {
                    let action = TradeAction::Rebalance;
                    actions.push(action);
                }
            }
            _ => {}
        }

        actions
    }

    pub fn is_close_signaled(&self, _strategy: TradingStrategy) -> TradeAction {
        let trend: TrendType = self.metrics.price.borrow().trend.clone();
        match trend {
            // TrendType::Down => TradeAction::SellClose,
            _ => TradeAction::None,
        }
    }

    fn get_fill_probability(&self, current_price: Decimal, target_price: Decimal) -> Decimal {
        if let Some(value_pair_map) = self.probability.fill_probability.get(&current_price) {
            if let Some(value_pair) = value_pair_map.get(&target_price) {
                let alpha = Decimal::from(value_pair.first);
                let beta = Decimal::from(value_pair.second);
                log::info!(
                    "value_pair: alpha = {}, beta = {}, current = {}, target = {}",
                    alpha,
                    beta,
                    current_price,
                    target_price
                );
                alpha / (alpha + beta)
            } else {
                Decimal::ZERO
            }
        } else {
            Decimal::ZERO
        }
    }

    fn get_market_make_actions(&self, min_tick: Decimal, mid_price: Decimal) -> Vec<TradeAction> {
        let mut actions: Vec<TradeAction> = vec![];
        let confidence = Decimal::ONE;

        if let Some((buy_price, sell_price)) = self.get_best_market_make_price(min_tick, mid_price)
        {
            let buy_action =
                TradeAction::BuyOpen(TradeDetail::new(Some(buy_price), None, confidence));
            actions.push(buy_action);

            let sell_action =
                TradeAction::SellOpen(TradeDetail::new(Some(sell_price), None, confidence));
            actions.push(sell_action);
        }

        actions
    }

    fn get_best_market_make_price(
        &self,
        min_tick: Decimal,
        mid_price: Decimal,
    ) -> Option<(Decimal, Decimal)> {
        let buy_offsets = (-200..=-1).map(|x| Decimal::from(x)).collect::<Vec<_>>();
        let sell_offsets = (1..=200).map(|x| Decimal::from(x)).collect::<Vec<_>>();

        let mut best_e = Decimal::MIN;
        let mut best_price = None;

        log::trace!("Fill Probabilities:");
        for (mid_price, value_pair_map) in self.probability.fill_probability.iter() {
            log::trace!("  mid_price: {}", mid_price);
            for (price, value_pair) in value_pair_map.iter() {
                let fill_probability =
                    (value_pair.first as f64) / ((value_pair.first + value_pair.second) as f64);
                log::trace!(
                    "    target_price: {}, fill_probability: {:.2}%",
                    price,
                    fill_probability * 100.0
                );
            }
        }

        for &buy_offset in &buy_offsets {
            for &sell_offset in &sell_offsets {
                if buy_offset >= sell_offset {
                    continue;
                }

                let buy_price = mid_price + buy_offset * min_tick;
                let sell_price = mid_price + sell_offset * min_tick;

                let bp = self.get_fill_probability(mid_price, buy_price);
                let sp = self.get_fill_probability(mid_price, sell_price);

                if bp.is_zero() || sp.is_zero() {
                    continue;
                }

                log::debug!(
                    "bp = {:.2}, sp = {:.2}, mid = {}, buy = {}, sell = {}",
                    bp,
                    sp,
                    mid_price,
                    buy_price,
                    sell_price
                );

                let one = Decimal::ONE;
                let eprofit = bp * sp * (sell_price - buy_price);
                let eloss = (one - (one - bp) * (one - sp) - bp * sp) * self.atr();
                let e = eprofit - eloss;

                if e > Decimal::ZERO && e > best_e {
                    best_e = e;
                    best_price = Some((buy_price, sell_price));

                    log::debug!(
                        "e = {}({} - {}), buy_price = {}, sell_price = {}, atr = {}",
                        e,
                        eprofit,
                        eloss,
                        buy_price,
                        sell_price,
                        self.atr(),
                    );
                }
            }
        }

        best_price
    }

    fn update_mean_and_variance(&mut self, period: usize) {
        let prices_window = if self.prices.len() > period {
            self.prices
                .iter()
                .skip(self.prices.len() - period)
                .collect::<Vec<_>>()
        } else {
            self.prices.iter().collect::<Vec<_>>()
        };

        let count = Decimal::from(prices_window.len());
        if count == Decimal::ZERO {
            return;
        }

        let sum: Decimal = prices_window.iter().map(|pp| pp.price).sum();
        let new_mean = sum / count;

        let new_variance = prices_window
            .iter()
            .map(|pp| {
                let diff = pp.price - new_mean;
                diff * diff
            })
            .sum::<Decimal>()
            / count;

        self.price_mean = new_mean;
        self.price_variance = new_variance;
    }

    fn update_rsi(&mut self) {
        let periods = [
            self.config.trading_period,
            self.config.short_period,
            self.config.long_period,
        ];
        let values: Vec<_> = periods
            .iter()
            .filter_map(|&period| self.calculate_rsi(period))
            .collect();

        if values.len() == periods.len() {
            self.metrics
                .rsi
                .borrow_mut()
                .update(values[0], values[1], values[2]);
        }
    }

    fn update_adx(&mut self) {
        let periods = [
            self.config.trading_period,
            self.config.short_period,
            self.config.long_period,
        ];
        let values: Vec<_> = periods
            .iter()
            .filter_map(|&period| self.calculate_adx(period))
            .collect();

        if values.len() == periods.len() {
            self.metrics.adx.update(values[0], values[1], values[2]);
        }
    }

    fn update_stochastic(&mut self) {
        let periods = [
            self.config.trading_period,
            self.config.short_period,
            self.config.long_period,
        ];
        let values: Vec<_> = periods
            .iter()
            .filter_map(|&period| self.calculate_stochastic(period))
            .collect();

        if values.len() == periods.len() {
            self.metrics
                .stochastic
                .update(values[0], values[1], values[2]);
        }
    }

    fn update_ema(&mut self, price: Decimal) {
        self.metrics.price.borrow_mut().update(price, price, price);
    }

    fn calculate_rsi(&self, period: usize) -> Option<Decimal> {
        let window_size = 10;

        if self.prices.len() < period {
            return None;
        }

        let mut gains = Decimal::ZERO;
        let mut losses = Decimal::ZERO;

        let recent_prices = self
            .prices
            .iter()
            .skip(self.prices.len().saturating_sub(period))
            .cloned()
            .collect::<Vec<_>>();

        for window in recent_prices.windows(window_size) {
            if let (Some(first), Some(last)) = (window.first(), window.last()) {
                let change = last.price - first.price;
                if change.is_sign_positive() {
                    gains += change;
                } else {
                    losses += change.abs();
                }
            }
        }

        let rs = if losses.is_zero() {
            Decimal::new(100, 0)
        } else {
            gains / losses
        };

        let one = Decimal::ONE;
        Some(one - (one / (one + rs)))
    }

    fn calculate_adx(&mut self, period: usize) -> Option<Decimal> {
        if self.prices.len() < period {
            return None;
        }

        let (high, low) = self.extract_high_low(period);
        let atr = (high - low) / Decimal::from(period);

        if atr.is_zero() {
            return None;
        }

        let mut plus_dm = Decimal::ZERO;
        let mut minus_dm = Decimal::ZERO;

        for price_point in &self
            .prices
            .iter()
            .skip(self.prices.len() - period)
            .collect::<Vec<_>>()
        {
            let up_move = high - price_point.price;
            let down_move = price_point.price - low;
            if up_move > down_move && up_move.is_sign_positive() {
                plus_dm += up_move;
            }
            if down_move > up_move && down_move.is_sign_positive() {
                minus_dm += down_move;
            }
        }

        let plus_di = plus_dm / atr;
        let minus_di = minus_dm / atr;

        let denominator = plus_di + minus_di;
        if denominator.is_zero() {
            return None;
        }

        Some((plus_di - minus_di).abs() / denominator)
    }

    fn calculate_stochastic(&mut self, period: usize) -> Option<Decimal> {
        if self.prices.len() < period {
            return None;
        }

        let (high, low) = self.extract_high_low(period);
        let current_close = match self.prices.iter().last() {
            Some(v) => v.price,
            None => {
                return None;
            }
        };

        if high == low {
            None
        } else {
            Some((current_close - low) / (high - low))
        }
    }

    fn update_atr(&mut self) {
        let period = self.config.trading_period as usize;
        if self.prices.len() <= period {
            // +1 is to include the previous_close price
            return;
        }

        if self.metrics.atr.is_none() {
            let mut tr_sum = Decimal::ZERO;
            for i in 0..=self.prices.len() - period {
                let (current_high, current_low) = self.extract_high_low(period);
                let previous_close = self.prices[i].price;

                let tr = [
                    current_high - current_low,
                    (current_high - previous_close).abs(),
                    (current_low - previous_close).abs(),
                ]
                .iter()
                .fold(Decimal::ZERO, |acc, &val| Decimal::max(acc, val));

                tr_sum += tr;
            }

            let initial_atr = tr_sum / Decimal::from(self.prices.len() - period + 1);
            self.metrics.atr = Some(initial_atr);
        } else {
            let (current_high, current_low) = self.extract_high_low(period);
            let previous_close = self.prices[self.prices.len() - period - 1].price;

            let current_tr = [
                current_high - current_low,
                (current_high - previous_close).abs(),
                (current_low - previous_close).abs(),
            ]
            .iter()
            .fold(Decimal::ZERO, |acc, &val| Decimal::max(acc, val));

            let alpha = Decimal::from(2) / (Decimal::from(period + 1));
            let new_atr = self.metrics.atr.unwrap() * (Decimal::ONE - alpha) + current_tr * alpha;
            self.metrics.atr = Some(new_atr);
        }
    }

    fn extract_high_low(&self, period: usize) -> (Decimal, Decimal) {
        self.prices
            .iter()
            .skip(self.prices.len() - period)
            .fold((Decimal::MIN, Decimal::MAX), |(high, low), pp| {
                (high.max(pp.price), low.min(pp.price))
            })
    }

    pub fn get_market_detail(&self) -> (Decimal, Decimal) {
        let is_crossover = self.metrics.price.borrow_mut().is_crossover().to_numeric();

        let trend = self.metrics.price.borrow().trend.to_numeric();

        return (is_crossover, trend);
    }
}
