use debot_db::{CandlePattern, PricePoint};
use debot_ml::RandomForest;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use smartcore::linalg::basic::matrix::DenseMatrix;

use crate::trend_metrics::TrendType;
use crate::{trade_action::TradeDetail, trend_metrics::TrendMetrics, TradeAction};
use rand::Rng;
use rayon::prelude::*;
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex};

#[derive(Debug, Clone)]
pub struct Candle {
    pub open: Decimal,
    pub high: Decimal,
    pub low: Decimal,
    pub close: Decimal,
    pub timestamp: i64,
}

impl Candle {
    pub fn new(open: Decimal, high: Decimal, low: Decimal, close: Decimal, timestamp: i64) -> Self {
        Self {
            open,
            high,
            low,
            close,
            timestamp,
        }
    }

    pub fn update(&mut self, price: Decimal) {
        if price > self.high {
            self.high = price;
        }
        if price < self.low {
            self.low = price;
        }
        self.close = price;
    }

    pub fn detect_pattern(candles: &[Candle]) -> CandlePattern {
        if candles.len() < 3 {
            return CandlePattern::None;
        }

        let latest = &candles[candles.len() - 1];
        let prev = &candles[candles.len() - 2];
        let prev2 = &candles[candles.len() - 3];

        let body = (latest.close - latest.open).abs();
        let upper_shadow = latest.high - Decimal::max(latest.open, latest.close);
        let lower_shadow = Decimal::min(latest.open, latest.close) - latest.low;

        // Hammer or Inverted Hammer
        if lower_shadow > body * Decimal::from(2) && upper_shadow < body {
            return CandlePattern::Hammer;
        } else if upper_shadow > body * Decimal::from(2) && lower_shadow < body {
            return CandlePattern::InvertedHammer;
        }

        // Engulfing (Bullish or Bearish)
        if latest.close > latest.open && latest.close > prev.high && latest.open < prev.low {
            return CandlePattern::BullishEngulfing;
        } else if latest.open > latest.close && latest.open > prev.high && latest.close < prev.low {
            return CandlePattern::BearishEngulfing;
        }

        // Doji
        if body < Decimal::new(1, 1) * (latest.high - latest.low) {
            return CandlePattern::Doji;
        }

        // Marubozu
        if upper_shadow.is_zero() && lower_shadow.is_zero() {
            return CandlePattern::Marubozu;
        }

        // Spinning Top
        if upper_shadow > body && lower_shadow > body {
            return CandlePattern::SpinningTop;
        }

        // Harami (Bullish or Bearish)
        if (prev.open > prev.close
            && latest.open > latest.close
            && latest.open < prev.close
            && latest.close > prev.open)
            || (prev.open < prev.close
                && latest.open < latest.close
                && latest.open > prev.close
                && latest.close < prev.open)
        {
            return CandlePattern::Harami;
        }

        // Harami Cross
        if prev.close == prev.open && (prev.high - prev.low) > (prev.close - prev.open).abs() {
            if (latest.open > prev.close && latest.close < prev.open)
                || (latest.open < prev.close && latest.close > prev.open)
            {
                return CandlePattern::HaramiCross;
            }
        }

        // Morning Star
        if prev2.open > prev2.close
            && prev.open < prev.close
            && latest.open > latest.close
            && latest.close > (prev.open + prev.close) / Decimal::new(2, 0)
        {
            return CandlePattern::MorningStar;
        }

        // Evening Star
        if prev2.open < prev2.close
            && prev.open > prev.close
            && latest.open < latest.close
            && latest.close < (prev.open + prev.close) / Decimal::new(2, 0)
        {
            return CandlePattern::EveningStar;
        }

        // Three White Soldiers
        if prev2.close < prev2.open
            && prev.close > prev.open
            && latest.close > latest.open
            && prev2.close < prev.close
            && prev.close < latest.close
            && latest.open > prev.close
        {
            return CandlePattern::ThreeWhiteSoldiers;
        }

        // Three Black Crows
        if prev2.close > prev2.open
            && prev.close < prev.open
            && latest.close < latest.open
            && prev2.close > prev.close
            && prev.close > latest.close
            && latest.open < prev.close
        {
            return CandlePattern::ThreeBlackCrows;
        }

        // Piercing Pattern
        if prev.open > prev.close
            && latest.open < latest.close
            && latest.open < prev.close
            && latest.close > (prev.open + prev.close) / Decimal::new(2, 0)
        {
            return CandlePattern::PiercingPattern;
        }

        // Dark Cloud Cover
        if prev.open < prev.close
            && latest.open > latest.close
            && latest.open > prev.close
            && latest.close < (prev.open + prev.close) / Decimal::new(2, 0)
        {
            return CandlePattern::DarkCloudCover;
        }

        CandlePattern::None
    }
}

#[derive(Debug, Clone)]
pub enum SampleTerm {
    TradingTerm,
    ShortTerm,
    LongTerm,
}

impl SampleTerm {
    pub fn to_numeric(&self) -> Decimal {
        match self {
            SampleTerm::TradingTerm => Decimal::new(1, 0),
            SampleTerm::ShortTerm => Decimal::new(2, 0),
            SampleTerm::LongTerm => Decimal::new(3, 0),
        }
    }
}

#[derive(Debug, Clone)]
struct MarketDataConfig {
    short_period: usize,
    long_period: usize,
    trading_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>,
    classification_cache: HashMap<TradingStrategy, Vec<Vec<i32>>>,
    regression_cache: HashMap<TradingStrategy, Vec<Vec<f64>>>,
}

#[derive(Clone, Copy, Debug, Eq, Hash)]
pub enum TradingStrategy {
    RandomWalk(TrendType),
    MeanReversion(TrendType),
}

impl TradingStrategy {
    pub fn trend_type(&self) -> &TrendType {
        match self {
            TradingStrategy::RandomWalk(t) | TradingStrategy::MeanReversion(t) => t,
        }
    }
}

impl PartialEq for TradingStrategy {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (TradingStrategy::RandomWalk(TrendType::Unknown), TradingStrategy::RandomWalk(_)) => {
                true
            }
            (TradingStrategy::RandomWalk(_), TradingStrategy::RandomWalk(TrendType::Unknown)) => {
                true
            }
            (
                TradingStrategy::RandomWalk(TrendType::Up),
                TradingStrategy::RandomWalk(TrendType::Up),
            ) => true,
            (
                TradingStrategy::RandomWalk(TrendType::Down),
                TradingStrategy::RandomWalk(TrendType::Down),
            ) => true,
            (
                TradingStrategy::MeanReversion(TrendType::Unknown),
                TradingStrategy::MeanReversion(_),
            ) => true,
            (
                TradingStrategy::MeanReversion(_),
                TradingStrategy::MeanReversion(TrendType::Unknown),
            ) => true,
            (
                TradingStrategy::MeanReversion(TrendType::Up),
                TradingStrategy::MeanReversion(TrendType::Up),
            ) => true,
            (
                TradingStrategy::MeanReversion(TrendType::Down),
                TradingStrategy::MeanReversion(TrendType::Down),
            ) => true,
            _ => false,
        }
    }
}

impl MarketData {
    pub fn new(
        name: String,
        short_period: usize,
        long_period: usize,
        trading_period: usize,
        max_size: usize,
        random_forest: Option<RandomForest>,
        only_read_price: bool,
    ) -> MarketData {
        let config = MarketDataConfig {
            short_period,
            long_period,
            trading_period,
            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,
            classification_cache: HashMap::new(),
            regression_cache: HashMap::new(),
        }
    }

    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) -> Option<(Decimal, Decimal)> {
        let period = self.config.trading_period as usize;
        let prices_len = self.prices.len();
        if prices_len < period {
            return None;
        }

        let range_start = prices_len - period;
        let mut min = Decimal::MAX;
        let mut max = Decimal::MIN;

        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));
        let price_point = PricePoint::new(
            price,
            timestamp,
            volume,
            num_trades,
            funding_rate,
            open_interest,
            oracle_price,
        );

        if self.config.only_read_price {
            return price_point;
        }

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

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

        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));
        }
    }

    fn candle_pattern_to_f64_array(&self) -> Vec<f64> {
        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();

        [
            candle_value_0,
            candle_value_1,
            candle_value_2,
            candle_value_3,
        ]
        .concat()
        .iter()
        .map(|&v| v.to_f64().unwrap())
        .collect()
    }

    pub fn precompute_models(
        &mut self,
        strategy: TradingStrategy,
        n: usize,
        take_profit_ratio: &Vec<Decimal>,
        atr_spread: &Vec<Option<Decimal>>,
        open_tick_count_max: &Vec<u32>,
        atr_term: &Vec<SampleTerm>,
        chunk_size: Option<usize>,
    ) {
        if self.random_forest.is_none() {
            return;
        }

        if self.is_metrics_none() {
            log::info!("precompute_models skipped: No metrics");
            return;
        }

        let chunk_size = chunk_size.unwrap_or(1000);

        let classification_cache = Arc::new(Mutex::new(self.classification_cache.clone()));
        let regression_cache = Arc::new(Mutex::new(self.regression_cache.clone()));

        let data: Vec<Vec<f64>> = (0..n)
            .map(|i| {
                let mut input = vec![
                    self.price().3.round_dp(4).to_f64().unwrap(),
                    self.price().4.round_dp(4).to_f64().unwrap(),
                    self.price().5.round_dp(4).to_f64().unwrap(),
                    self.atr().1.round_dp(4).to_f64().unwrap(),
                    self.atr().2.round_dp(4).to_f64().unwrap(),
                    self.atr().3.round_dp(4).to_f64().unwrap(),
                    self.atr().4.round_dp(4).to_f64().unwrap(),
                    self.atr().5.round_dp(4).to_f64().unwrap(),
                    self.rsi().1.round_dp(4).to_f64().unwrap(),
                    self.rsi().2.round_dp(4).to_f64().unwrap(),
                    self.rsi().3.round_dp(4).to_f64().unwrap(),
                    self.rsi().4.round_dp(4).to_f64().unwrap(),
                    self.rsi().5.round_dp(4).to_f64().unwrap(),
                    self.last_volume()
                        .unwrap_or_default()
                        .round_dp(4)
                        .to_f64()
                        .unwrap(),
                    self.last_num_trades().unwrap_or_default().to_f64().unwrap(),
                    self.last_funding_rate()
                        .unwrap_or_default()
                        .to_f64()
                        .unwrap(),
                    self.last_open_interest()
                        .unwrap_or_default()
                        .to_f64()
                        .unwrap(),
                    (self.last_oracle_price().unwrap_or_default().round_dp(4)
                        - self.price().0.round_dp(4))
                    .to_f64()
                    .unwrap(),
                    0.0, // input_19 placeholder
                    atr_spread[i].unwrap_or_default().to_f64().unwrap(),
                    open_tick_count_max[i].to_f64().unwrap(),
                    if take_profit_ratio[i].is_zero() {
                        0.0
                    } else {
                        1.0
                    },
                    atr_term[i].to_numeric().round_dp(4).to_f64().unwrap(),
                ];

                input.extend(self.candle_pattern_to_f64_array());
                input
            })
            .collect();

        data.par_chunks(chunk_size).for_each(|batch| {
            let x = DenseMatrix::from_2d_vec(&batch.to_vec());

            if let Some(random_forest) = &self.random_forest {
                let classification_results = random_forest.classify_profitability(&x);
                let regression_results = random_forest.regress_profit_ratio(&x);

                {
                    let mut class_cache = classification_cache.lock().unwrap();
                    class_cache.insert(
                        strategy,
                        classification_results
                            .into_iter()
                            .map(|v| vec![v])
                            .collect(),
                    );
                }

                {
                    let mut reg_cache = regression_cache.lock().unwrap();
                    reg_cache.insert(
                        strategy,
                        regression_results.into_iter().map(|v| vec![v]).collect(),
                    );
                }
            }
        });

        self.classification_cache = Arc::try_unwrap(classification_cache)
            .unwrap()
            .into_inner()
            .unwrap();
        self.regression_cache = Arc::try_unwrap(regression_cache)
            .unwrap()
            .into_inner()
            .unwrap();
    }

    pub fn is_open_signaled(
        &self,
        strategy: TradingStrategy,
        index: usize,
        take_profit_ratio: Decimal,
    ) -> Vec<TradeAction> {
        let mut actions: Vec<TradeAction> = vec![];

        match strategy {
            TradingStrategy::RandomWalk(trend_type) => {
                if Self::random_bool(0.1) {
                    let action = Self::create_open_action(trend_type);
                    actions.push(action.clone());
                }
            }
            TradingStrategy::MeanReversion(_) => {
                let classification_cache = match self.classification_cache.get(&strategy) {
                    Some(cache) => cache,
                    None => {
                        log::info!(
                            "is_open_signaled: Classification cache is empty for {:?}",
                            strategy
                        );
                        return actions;
                    }
                };

                let regression_cache = match self.regression_cache.get(&strategy) {
                    Some(cache) => cache,
                    None => {
                        log::info!(
                            "is_open_signaled: Regression cache is empty for {:?}",
                            strategy
                        );
                        return actions;
                    }
                };

                let classification_result = classification_cache[index][0];
                let regression_result = regression_cache[index][0];

                if classification_result == 1 {
                    let take_profit_ratio_f64 = take_profit_ratio.to_f64().unwrap_or(0.0);
                    if take_profit_ratio.is_zero() || regression_result >= take_profit_ratio_f64 {
                        if Self::random_bool(0.5) {
                            log::debug!(
                                "signal: predict = {:.3}, config = {:.3}",
                                regression_result,
                                take_profit_ratio_f64
                            );
                            let action = Self::create_open_action(strategy.trend_type().clone());
                            actions.push(action.clone());
                        }
                    } else {
                        log::debug!(
                            "ignore signal for {:?}, predict = {:.3}",
                            strategy,
                            regression_result
                        );
                    }
                }
            }
        }

        actions
    }

    fn random_bool(probability: f64) -> bool {
        let mut rng = rand::thread_rng();
        rng.gen_bool(probability)
    }

    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()
    }

    pub fn is_close_signaled(
        &self,
        _strategy: TradingStrategy,
        _amount_in_usd: Decimal,
        _is_profitable_position: bool,
    ) -> TradeAction {
        TradeAction::None
    }

    fn create_open_action(trend: TrendType) -> TradeAction {
        match trend {
            TrendType::Up => TradeAction::BuyOpen(TradeDetail::new(None, None, Decimal::ONE)),
            TrendType::Down => TradeAction::SellOpen(TradeDetail::new(None, None, Decimal::ONE)),
            _ => 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,
        };

        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))
            })
    }
}
