use debot_db::{CandlePattern, PricePoint, SampleTerm, TradingStrategy, TrendType};
use debot_ml::RandomForest;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use smartcore_proba::linalg::basic::arrays::Array2;
use smartcore_proba::linalg::basic::matrix::DenseMatrix;

use crate::candle::Candle;
use crate::{trade_action::TradeDetail, trend_metrics::TrendMetrics, TradeAction};
use std::collections::VecDeque;

const MAX_LOSS_PROB: f64 = 0.6;
const MIN_EXPECTED_SCORE: f64 = 0.3;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BreakoutDirection {
    Up,
    Down,
    Both,
}

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

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

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

pub struct MarketData {
    name: String,
    prices: VecDeque<PricePoint>,
    config: MarketDataConfig,
    metrics: MarketDataMetrics,
    random_forest: Option<RandomForest>,
    candles_5m: Vec<Candle>,
    candles_1h: Vec<Candle>,
    candles_4h: Vec<Candle>,
    current_candle_5m: Option<Candle>,
    current_candle_1h: Option<Candle>,
    current_candle_4h: Option<Candle>,
}

impl MarketData {
    pub fn new(
        name: String,
        short_period: usize,
        long_period: usize,
        trading_period: usize,
        flash_crash_period: usize,
        max_size: usize,
        random_forest: Option<RandomForest>,
        only_read_price: bool,
    ) -> MarketData {
        let config = MarketDataConfig {
            short_period,
            long_period,
            trading_period,
            flash_crash_period,
            max_size: if only_read_price {
                long_period * 3
            } else {
                max_size
            },
            only_read_price,
        };
        let metrics = MarketDataMetrics::new(short_period, long_period, trading_period);
        MarketData {
            name,
            prices: VecDeque::with_capacity(max_size),
            config,
            metrics,
            random_forest,
            candles_5m: Vec::new(),
            candles_1h: Vec::new(),
            candles_4h: Vec::new(),
            current_candle_5m: None,
            current_candle_1h: None,
            current_candle_4h: None,
        }
    }

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

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

    pub fn last_volume(&self) -> Option<Decimal> {
        self.prices.iter().last().and_then(|p| p.volume)
    }

    pub fn last_num_trades(&self) -> Option<u64> {
        self.prices.iter().last().and_then(|p| p.num_trades)
    }

    pub fn last_funding_rate(&self) -> Option<Decimal> {
        self.prices.iter().last().and_then(|p| p.funding_rate)
    }

    pub fn last_open_interest(&self) -> Option<Decimal> {
        self.prices.iter().last().and_then(|p| p.open_interest)
    }

    pub fn last_oracle_price(&self) -> Option<Decimal> {
        self.prices.iter().last().and_then(|p| p.oracle_price)
    }

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

    pub fn atr_by_term(&self, term: &SampleTerm) -> Decimal {
        let atrs = self.atr();
        match term {
            SampleTerm::TradingTerm => atrs.0,
            SampleTerm::ShortTerm => atrs.1,
            SampleTerm::LongTerm => atrs.2,
        }
    }

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

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

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

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

    pub fn candle_pattern(&self) -> (CandlePattern, CandlePattern, CandlePattern, CandlePattern) {
        // Current and previous patterns for 5m candles
        let candle_pattern_5m = Candle::detect_pattern(&self.candles_5m);
        let prev_candle_pattern_5m = if self.candles_5m.len() > 1 {
            Candle::detect_pattern(&self.candles_5m[..self.candles_5m.len() - 1])
        } else {
            CandlePattern::None
        };

        // Current and previous patterns for 1h candles
        let candle_pattern_1h = Candle::detect_pattern(&self.candles_1h);
        let prev_candle_pattern_1h = if self.candles_1h.len() > 1 {
            Candle::detect_pattern(&self.candles_1h[..self.candles_1h.len() - 1])
        } else {
            CandlePattern::None
        };

        // Current and previous patterns for 4h candles
        let _candle_pattern_4h = Candle::detect_pattern(&self.candles_4h);
        let _prev_candle_pattern_4h = if self.candles_4h.len() > 1 {
            Candle::detect_pattern(&self.candles_4h[..self.candles_4h.len() - 1])
        } else {
            CandlePattern::None
        };

        (
            candle_pattern_5m,
            candle_pattern_1h,
            prev_candle_pattern_5m,
            prev_candle_pattern_1h,
        )
    }

    pub fn get_min_max_price(&self, period: usize) -> Option<(Decimal, Decimal)> {
        let prices_len = self.prices.len();
        if prices_len <= period {
            return None;
        }

        let range_start = prices_len.saturating_sub(period);
        if range_start >= prices_len {
            return None;
        }

        let first_price = self.prices[range_start].price;
        let mut min = first_price;
        let mut max = first_price;

        for i in range_start..prices_len {
            let price = self.prices[i].price;
            min = Decimal::min(min, price);
            max = Decimal::max(max, price);
        }

        Some((min, max))
    }

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

        let price =
            price.unwrap_or_else(|| self.prices.back().map_or(Decimal::ZERO, |pp| pp.price));

        self.update_ema(price);
        self.update_rsi();
        self.update_adx();
        self.update_stochastic();
        self.update_atr();

        let price_point = PricePoint::new(
            price,
            timestamp,
            volume,
            num_trades,
            funding_rate,
            open_interest,
            oracle_price,
            None,
        );

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

        if self.config.only_read_price {
            return price_point;
        }

        let mut candles_5m = std::mem::take(&mut self.candles_5m);
        let mut current_candle_5m = self.current_candle_5m.take();
        self.update_candle(
            price,
            price_point.timestamp,
            300,
            &mut candles_5m,
            &mut current_candle_5m,
        );
        self.candles_5m = candles_5m;
        self.current_candle_5m = current_candle_5m;

        let mut candles_1h = std::mem::take(&mut self.candles_1h);
        let mut current_candle_1h = self.current_candle_1h.take();
        self.update_candle(
            price,
            price_point.timestamp,
            3600,
            &mut candles_1h,
            &mut current_candle_1h,
        );
        self.candles_1h = candles_1h;
        self.current_candle_1h = current_candle_1h;

        let mut candles_4h = std::mem::take(&mut self.candles_4h);
        let mut current_candle_4h = self.current_candle_4h.take();
        self.update_candle(
            price,
            price_point.timestamp,
            14400,
            &mut candles_4h,
            &mut current_candle_4h,
        );
        self.candles_4h = candles_4h;
        self.current_candle_4h = current_candle_4h;

        price_point
    }

    fn update_candle(
        &mut self,
        price: Decimal,
        timestamp: i64,
        interval: i64,
        candles: &mut Vec<Candle>,
        current_candle: &mut Option<Candle>,
    ) {
        let rounded_timestamp = (timestamp / interval) * interval;

        let one_day_ago = timestamp - 86400;
        candles.retain(|candle| candle.timestamp >= one_day_ago);

        if let Some(ref mut candle) = current_candle {
            if candle.timestamp == rounded_timestamp {
                candle.update(price);
            } else {
                candles.push(candle.clone());
                *current_candle = Some(Candle::new(price, price, price, price, rounded_timestamp));
            }
        } else {
            *current_candle = Some(Candle::new(price, price, price, price, rounded_timestamp));
        }
    }

    pub fn is_signaled(
        &self,
        strategy: TradingStrategy,
        _index: usize,
        take_profit_ratio: Decimal,
        atr_spread: Option<Decimal>,
        open_tick_count_max: u32,
        atr_term: &SampleTerm,
        _predict_profit_ratio: bool,
        _seed: Option<u64>,
        breakout_atr_multiplier: Decimal,
        rsi_threshold: Decimal,
        price_lookback: usize,
        grid_entry_spread: i64,
        atr_multiplier: Decimal,
        min_percentage: Option<Decimal>,
    ) -> Vec<TradeAction> {
        let mut actions: Vec<TradeAction> = vec![];

        if self.is_metrics_none() {
            log::debug!("is_open_signaled skipped: No metrics");
            return actions;
        }

        let (candle_pattern_0, candle_pattern_1, candle_pattern_2, candle_pattern_3) =
            self.candle_pattern();
        let candle_value_0 = candle_pattern_0.to_one_hot();
        let candle_value_1 = candle_pattern_1.to_one_hot();
        let candle_value_2 = candle_pattern_2.to_one_hot();
        let candle_value_3 = candle_pattern_3.to_one_hot();

        match strategy {
            TradingStrategy::RandomFlashCrash => {
                if let Some(price_change) = self.detect_flash_crash(atr_multiplier, min_percentage)
                {
                    if price_change < Decimal::ZERO {
                        let action = Self::create_open_action(TrendType::Up, None, None);
                        actions.push(action.clone());
                        return actions;
                    }
                }
                return actions;
            }
            TradingStrategy::RandomInago(trend_type) => {
                if let Some(break_out) =
                    self.detect_breakout(self.config.trading_period, breakout_atr_multiplier)
                {
                    if break_out == BreakoutDirection::Up && trend_type == TrendType::Up
                        || break_out == BreakoutDirection::Down && trend_type == TrendType::Down
                    {
                        let action = Self::create_open_action(trend_type, None, None);
                        actions.push(action.clone());
                        return actions;
                    }
                }
                return actions;
            }
            TradingStrategy::RandomInagoReversion(trend_type) => {
                if let Some(break_out) =
                    self.detect_breakout(self.config.trading_period, breakout_atr_multiplier)
                {
                    if break_out == BreakoutDirection::Up && trend_type == TrendType::Down
                        || break_out == BreakoutDirection::Down && trend_type == TrendType::Up
                    {
                        let action = Self::create_open_action(trend_type, None, None);
                        actions.push(action.clone());
                        return actions;
                    }
                }
                return actions;
            }
            TradingStrategy::RandomGridEntry(trend_type) => {
                let action = Self::create_open_action(trend_type, None, None);
                actions.push(action.clone());
                return actions;
            }
            TradingStrategy::Inago(trend_type) => {
                if let Some(break_out) =
                    self.detect_breakout(self.config.trading_period, breakout_atr_multiplier)
                {
                    match (break_out, trend_type) {
                        (BreakoutDirection::Up, TrendType::Up)
                        | (BreakoutDirection::Down, TrendType::Down) => {}
                        _ => {
                            return actions;
                        }
                    }
                } else {
                    return actions;
                }
            }
            TradingStrategy::InagoReversion(trend_type) => {
                if let Some(break_out) =
                    self.detect_breakout(self.config.trading_period, breakout_atr_multiplier)
                {
                    match (break_out, trend_type) {
                        (BreakoutDirection::Up, TrendType::Down)
                        | (BreakoutDirection::Down, TrendType::Up) => {}
                        _ => {
                            return actions;
                        }
                    }
                } else {
                    return actions;
                }
            }
            TradingStrategy::FlashCrash => {
                if let Some(price_change) = self.detect_flash_crash(atr_multiplier, min_percentage)
                {
                    if price_change > Decimal::ZERO {
                        return actions;
                    }
                } else {
                    return actions;
                }
            }
            TradingStrategy::GridEntry(_) => {}
        }

        let trend_type = strategy.trend_type();

        let x = DenseMatrix::from_2d_array(&[&[
            // input_1
            self.price()
                .3
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_2
            self.price()
                .4
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_3
            self.price()
                .5
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_4
            self.atr()
                .1
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_5
            self.atr()
                .2
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_6
            self.atr()
                .3
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_7
            self.atr()
                .4
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_8
            self.atr()
                .5
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_9
            self.rsi()
                .1
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_10
            self.rsi()
                .2
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_11
            self.rsi()
                .3
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_12
            self.rsi()
                .4
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_13
            self.rsi()
                .5
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_14
            self.last_volume()
                .unwrap_or_default()
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_15
            self.last_num_trades()
                .unwrap_or_default()
                .to_f64()
                .expect("conversion failed"),
            // input_16
            self.last_funding_rate()
                .unwrap_or_default()
                .to_f64()
                .expect("conversion failed"),
            // input_17
            self.last_open_interest()
                .unwrap_or_default()
                .to_f64()
                .expect("conversion failed"),
            // input_18
            (self.last_oracle_price().unwrap_or_default().round_dp(4) - self.price().0.round_dp(4))
                .to_f64()
                .expect("conversion failed"),
            // input_19
            grid_entry_spread.to_f64().expect("conversion failed"),
            // input_20
            atr_spread
                .unwrap_or_default()
                .to_f64()
                .expect("conversion failed"),
            // input_21
            open_tick_count_max.to_f64().expect("conversion failed"),
            // input_22
            if take_profit_ratio.is_zero() {
                0.0
            } else {
                1.0
            },
            // input_23
            atr_term
                .to_numeric()
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_24
            0.0,
            // input_25
            0.0,
            // input_26
            breakout_atr_multiplier
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_27
            rsi_threshold
                .round_dp(4)
                .to_f64()
                .expect("conversion failed"),
            // input_28
            price_lookback.to_f64().expect("conversion failed"),
            // input_29
            0.0,
            // input_30
            candle_value_0[0].to_f64().expect("conversion failed"),
            // input_31
            candle_value_0[1].to_f64().expect("conversion failed"),
            // input_32
            candle_value_0[2].to_f64().expect("conversion failed"),
            // input_33
            candle_value_0[3].to_f64().expect("conversion failed"),
            // input_34
            candle_value_0[4].to_f64().expect("conversion failed"),
            // input_35
            candle_value_0[5].to_f64().expect("conversion failed"),
            // input_36
            candle_value_0[6].to_f64().expect("conversion failed"),
            // input_37
            candle_value_0[7].to_f64().expect("conversion failed"),
            // input_38
            candle_value_0[8].to_f64().expect("conversion failed"),
            // input_39
            candle_value_0[9].to_f64().expect("conversion failed"),
            // input_40
            candle_value_0[10].to_f64().expect("conversion failed"),
            // input_41
            candle_value_0[11].to_f64().expect("conversion failed"),
            // input_42
            candle_value_0[12].to_f64().expect("conversion failed"),
            // input_43
            candle_value_0[13].to_f64().expect("conversion failed"),
            // input_44
            candle_value_0[14].to_f64().expect("conversion failed"),
            // input_45
            candle_value_0[15].to_f64().expect("conversion failed"),
            // input_46
            candle_value_1[0].to_f64().expect("conversion failed"),
            // input_47
            candle_value_1[1].to_f64().expect("conversion failed"),
            // input_48
            candle_value_1[2].to_f64().expect("conversion failed"),
            // input_49
            candle_value_1[3].to_f64().expect("conversion failed"),
            // input_50
            candle_value_1[4].to_f64().expect("conversion failed"),
            // input_51
            candle_value_1[5].to_f64().expect("conversion failed"),
            // input_52
            candle_value_1[6].to_f64().expect("conversion failed"),
            // input_53
            candle_value_1[7].to_f64().expect("conversion failed"),
            // input_54
            candle_value_1[8].to_f64().expect("conversion failed"),
            // input_55
            candle_value_1[9].to_f64().expect("conversion failed"),
            // input_56
            candle_value_1[10].to_f64().expect("conversion failed"),
            // input_57
            candle_value_1[11].to_f64().expect("conversion failed"),
            // input_58
            candle_value_1[12].to_f64().expect("conversion failed"),
            // input_59
            candle_value_1[13].to_f64().expect("conversion failed"),
            // input_60
            candle_value_1[14].to_f64().expect("conversion failed"),
            // input_61
            candle_value_1[15].to_f64().expect("conversion failed"),
            // input_62
            candle_value_2[0].to_f64().expect("conversion failed"),
            // input_63
            candle_value_2[1].to_f64().expect("conversion failed"),
            // input_64
            candle_value_2[2].to_f64().expect("conversion failed"),
            // input_65
            candle_value_2[3].to_f64().expect("conversion failed"),
            // input_66
            candle_value_2[4].to_f64().expect("conversion failed"),
            // input_67
            candle_value_2[5].to_f64().expect("conversion failed"),
            // input_68
            candle_value_2[6].to_f64().expect("conversion failed"),
            // input_69
            candle_value_2[7].to_f64().expect("conversion failed"),
            // input_70
            candle_value_2[8].to_f64().expect("conversion failed"),
            // input_71
            candle_value_2[9].to_f64().expect("conversion failed"),
            // input_72
            candle_value_2[10].to_f64().expect("conversion failed"),
            // input_73
            candle_value_2[11].to_f64().expect("conversion failed"),
            // input_74
            candle_value_2[12].to_f64().expect("conversion failed"),
            // input_75
            candle_value_2[13].to_f64().expect("conversion failed"),
            // input_76
            candle_value_2[14].to_f64().expect("conversion failed"),
            // input_77
            candle_value_2[15].to_f64().expect("conversion failed"),
            // input_78
            candle_value_3[0].to_f64().expect("conversion failed"),
            // input_79
            candle_value_3[1].to_f64().expect("conversion failed"),
            // input_80
            candle_value_3[2].to_f64().expect("conversion failed"),
            // input_81
            candle_value_3[3].to_f64().expect("conversion failed"),
            // input_82
            candle_value_3[4].to_f64().expect("conversion failed"),
            // input_83
            candle_value_3[5].to_f64().expect("conversion failed"),
            // input_84
            candle_value_3[6].to_f64().expect("conversion failed"),
            // input_85
            candle_value_3[7].to_f64().expect("conversion failed"),
            // input_86
            candle_value_3[8].to_f64().expect("conversion failed"),
            // input_87
            candle_value_3[9].to_f64().expect("conversion failed"),
            // input_88
            candle_value_3[10].to_f64().expect("conversion failed"),
            // input_89
            candle_value_3[11].to_f64().expect("conversion failed"),
            // input_90
            candle_value_3[12].to_f64().expect("conversion failed"),
            // input_91
            candle_value_3[13].to_f64().expect("conversion failed"),
            // input_92
            candle_value_3[14].to_f64().expect("conversion failed"),
            // input_93
            candle_value_3[15].to_f64().expect("conversion failed"),
        ]]);

        log::trace!("num of dense matrix x = {:?}", x);

        if let Some(rf) = &self.random_forest {
            match x {
                Ok(ref x_matrix) => {
                    let proba = rf.predict_proba(x_matrix);
                    let p_row = proba.get_row(0);
                    let p: Vec<f64> = p_row.iterator(0).copied().collect();

                    let expected_score = -1.0 * p[0] + 1.0 * p[1] + 2.0 * p[2];
                    let loss_prob = p[0];

                    log::warn!(
                        "PredictProba -> Loss: {:.3}, ExpiredProfit: {:.3}, TakeProfit: {:.3}, ExpectedScore: {:.3}",
                        p[0], p[1], p[2], expected_score
                    );

                    if loss_prob > MAX_LOSS_PROB {
                        log::info!("Skip: Loss probability {:.3} too high", loss_prob);
                        return actions;
                    }

                    if expected_score > MIN_EXPECTED_SCORE {
                        let label = [0, 1, 2]
                            .iter()
                            .enumerate()
                            .max_by(|a, b| p[a.0].partial_cmp(&p[b.0]).unwrap())
                            .map(|(_, &label)| label)
                            .unwrap_or(1);

                        actions.push(Self::create_open_action(*trend_type, None, Some(label)));
                    }
                }
                Err(e) => {
                    log::error!("Failed to build DenseMatrix: {:?}", e);
                }
            }
        }

        actions
    }

    fn is_metrics_none(&self) -> bool {
        self.metrics.adx.get().is_none()
            || self.metrics.atr.get().is_none()
            || self.metrics.price.get().is_none()
            || self.metrics.rsi.get().is_none()
            || self.metrics.stochastic.get().is_none()
    }

    fn create_open_action(
        trend: TrendType,
        tick_to_fill: Option<u32>,
        label: Option<i32>,
    ) -> TradeAction {
        let confidence = Decimal::from(label.unwrap_or(1)) / Decimal::new(2, 0);
        match trend {
            TrendType::Up => {
                TradeAction::BuyOpen(TradeDetail::new(None, None, confidence, tick_to_fill))
            }
            TrendType::Down => {
                TradeAction::SellOpen(TradeDetail::new(None, None, confidence, tick_to_fill))
            }
            _ => TradeAction::None,
        }
    }

    #[allow(dead_code)]
    fn create_close_action(trend: TrendType) -> TradeAction {
        match trend {
            TrendType::Up => {
                TradeAction::BuyClose(TradeDetail::new(None, None, Decimal::ONE, None))
            }
            TrendType::Down => {
                TradeAction::SellClose(TradeDetail::new(None, None, Decimal::ONE, None))
            }
            _ => TradeAction::None,
        }
    }

    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.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.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 (atr, high, low) = match self.calculate_atr(period) {
            Some((atr, high, low)) => (atr, high, low),
            None => return None,
        };

        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(&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 calculate_atr(&self, period: usize) -> Option<(Decimal, Decimal, Decimal)> {
        if self.prices.len() <= period {
            // +1 is to include the previous_close price
            return None;
        }

        let (current_high, current_low) = self.extract_high_low(period);
        let previous_close = self.prices[self.prices.len() - period - 1].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));

        Some((tr, current_high, current_low))
    }

    fn update_atr(&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_atr(period))
            .map(|x| x.0)
            .collect();

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

    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 detect_flash_crash(
        &self,
        atr_multiplier: Decimal,
        min_percentage: Option<Decimal>,
    ) -> Option<Decimal> {
        let current_price = self.last_price();
        let atr = self.atr().0;

        if atr.is_zero() || current_price.is_zero() {
            return None;
        }

        if let Some((min_price, max_price)) = self.get_min_max_price(self.config.flash_crash_period)
        {
            if min_price.is_zero()
                || max_price.is_zero()
                || min_price.is_sign_negative()
                || max_price.is_sign_negative()
            {
                log::warn!(
                    "Invalid min/max price detected: min_price={} max_price={}",
                    min_price,
                    max_price
                );
                return None;
            }

            let atr_threshold = atr * atr_multiplier;
            let percentage_threshold = current_price * min_percentage.unwrap_or_default();
            let threshold = Decimal::max(atr_threshold, percentage_threshold);

            for (label, ref_price) in [("dropped", min_price), ("surged", max_price)] {
                let price_change = (current_price - ref_price).round_dp(5);
                log::info!(
                    "atr_threshold: {:.2}, threshold: {:.2}, price_change: {:.2}",
                    atr_threshold,
                    threshold,
                    price_change
                );

                if price_change.abs() > threshold {
                    log::info!(
                        "Flash crash detected: Price {} rapidly! Change: {}({:.2} - {:.2}) (Threshold: {:.2})",
                        label,
                        price_change,
                        current_price,
                        ref_price,
                        threshold
                    );
                    return Some(price_change);
                }
            }
        }
        None
    }

    fn detect_breakout(
        &self,
        period: usize,
        breakout_atr_multiplier: Decimal,
    ) -> Option<BreakoutDirection> {
        if self.prices.len() < period * 2 {
            log::info!(
                "detect_breakout skipped: not enough price data (len={}, required={})",
                self.prices.len(),
                period * 2
            );
            return None;
        }

        // Use ATR as a threshold for breakout detection.
        let atr = self.atr().0;
        if atr.is_zero() {
            log::warn!("ATR is zero, breakout detection is not possible");
            return None;
        }
        // Set threshold as ATR multiplied by the provided multiplier.
        let threshold = atr * breakout_atr_multiplier;

        // Get current period prices by iterating over the last `period` elements.
        let current_prices_iter = self.prices.iter().skip(self.prices.len() - period);
        let current_min = current_prices_iter.clone().map(|p| p.price).min()?;
        let current_max = self
            .prices
            .iter()
            .skip(self.prices.len() - period)
            .map(|p| p.price)
            .max()?;

        // Get previous period prices by iterating over the `period` elements before the current range.
        let prev_prices_iter = self
            .prices
            .iter()
            .skip(self.prices.len() - period * 2)
            .take(period);
        let prev_min = prev_prices_iter.clone().map(|p| p.price).min()?;
        let prev_max = self
            .prices
            .iter()
            .skip(self.prices.len() - period * 2)
            .take(period)
            .map(|p| p.price)
            .max()?;

        // Check for a clear upward breakout.
        let up_break = current_max > prev_max + threshold;
        // Check for a clear downward breakout.
        let down_break = current_min < prev_min - threshold;

        let debug_message = format!(
            "current_max={:.2}, current_min={:.2}, prev_max={:.2}, prev_min={:.2}, threshold={:.2}, up_break={}, down_break={}",
            current_max,
            current_min,
            prev_max,
            prev_min,
            threshold,
            up_break,
            down_break
        );

        match (up_break, down_break) {
            (true, true) => {
                log::info!("Breakout detected: Both directions: {}", debug_message);
                Some(BreakoutDirection::Both)
            }
            (true, false) => {
                log::info!("Breakout detected: Up: {}", debug_message);
                Some(BreakoutDirection::Up)
            }
            (false, true) => {
                log::info!("Breakout detected: Down: {}", debug_message);
                Some(BreakoutDirection::Down)
            }
            (false, false) => None,
        }
    }

    /// Detects short-term trend (Up, Down, or None) based on ensemble of RSI, ATR, and price change.
    pub fn detect_trade_term_trend(
        &self,
        rsi_threshold: Decimal,
        price_lookback: usize,
    ) -> Option<TrendType> {
        if self.prices.len() < price_lookback + 1 {
            log::debug!("Not enough data to detect short-term trend");
            return None;
        }

        let current_price = self.last_price();
        let past_price = self.prices[self.prices.len() - 1 - price_lookback].price;
        let price_change = current_price - past_price;
        let price_return = price_change / past_price;

        let rsi = self.rsi().0;
        let atr = self.atr().0;
        let adx = self.adx().0;

        // Voting system with thresholds
        let mut up_votes = 0;
        let mut down_votes = 0;

        // RSI signal
        if rsi > rsi_threshold {
            up_votes += 1;
        } else if rsi < Decimal::ONE - rsi_threshold {
            down_votes += 1;
        }

        // Price return signal
        if price_return > Decimal::new(1, 2) {
            up_votes += 1;
        } else if price_return < Decimal::new(-1, 2) {
            down_votes += 1;
        }

        // ATR signal: volatility breakout
        if atr > Decimal::ZERO && price_change.abs() > atr {
            if price_change.is_sign_positive() {
                up_votes += 1;
            } else {
                down_votes += 1;
            }
        }

        // ADX signal: trend strength confirmation
        if adx > Decimal::new(2, 1) {
            // ADX > 20
            if price_change.is_sign_positive() {
                up_votes += 1;
            } else {
                down_votes += 1;
            }
        }

        // Final decision based on majority
        match (up_votes, down_votes) {
            (u, d) if u > d && u >= 2 => Some(TrendType::Up),
            (u, d) if d > u && d >= 2 => Some(TrendType::Down),
            _ => None,
        }
    }
}
