use crate::{
    edge_value::EdgeValue, trend_metrics::TrendMetrics, trend_value::ValueChange, PricePoint,
};
use std::collections::{HashMap, VecDeque};

#[derive(Debug, Clone)]
pub struct Prediction {
    pub confidence: f64,
    pub price: f64,
}

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

#[derive(Debug, Clone)]
struct MarketDataMetrics {
    price: TrendMetrics,
    rsi: TrendMetrics,
    adx: TrendMetrics,
    stochastic: TrendMetrics,
    bollinger_band: EdgeValue,
    atr: HashMap<usize, f64>,
    atr_ma: HashMap<usize, f64>,
    confidence: f64,
}

impl MarketDataMetrics {
    fn new(short_period: usize, long_period: usize, cross_effective_duration_secs: i64) -> Self {
        Self {
            price: TrendMetrics::new(short_period, long_period, cross_effective_duration_secs),
            rsi: TrendMetrics::new(short_period, long_period, cross_effective_duration_secs),
            adx: TrendMetrics::new(short_period, long_period, cross_effective_duration_secs),
            stochastic: TrendMetrics::new(short_period, long_period, cross_effective_duration_secs),
            atr: HashMap::new(),
            atr_ma: HashMap::new(),
            confidence: 0.0,
            bollinger_band: EdgeValue::default(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct MarketData {
    name: String,
    prices: VecDeque<PricePoint>,
    last_price: f64,
    first_timestamp: Option<i64>,
    config: MarketDataConfig,
    metrics: MarketDataMetrics,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TradingStrategy {
    TrendFollowLong,
    TrendFollowShort,
    MeanReversionLong,
    MeanReversionShort,
}

impl MarketData {
    pub fn new(
        name: String,
        short_period: usize,
        long_period: usize,
        trading_period: usize,
        max_size: usize,
        cross_effective_duration_secs: i64,
    ) -> MarketData {
        let config = MarketDataConfig {
            short_period,
            long_period,
            trading_period,
            max_size,
        };
        let metrics =
            MarketDataMetrics::new(short_period, long_period, cross_effective_duration_secs);

        MarketData {
            name,
            prices: VecDeque::with_capacity(max_size),
            last_price: 0.0,
            first_timestamp: None,
            config,
            metrics,
        }
    }

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

    pub fn atr(&self, period: usize) -> f64 {
        match self.metrics.atr.get(&period).copied() {
            Some(v) => v,
            None => 0.0,
        }
    }

    pub fn atr_ma(&self, period: usize) -> f64 {
        match self.metrics.atr_ma.get(&period).copied() {
            Some(v) => v,
            None => 0.0,
        }
    }

    pub fn add_price(&mut self, price: Option<f64>, 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 {
                    0.0
                } else {
                    match self.prices.iter().last() {
                        Some(v) => v.price,
                        None => 0.0,
                    }
                }
            }
        };

        let mut price_point = PricePoint::new(
            price,
            timestamp,
            self.metrics.confidence,
            self.metrics.bollinger_band.upper,
            self.metrics.bollinger_band.lower,
        );

        if let Some(prev_point) = self.prices.iter().last() {
            if let Some(relative_time) = price_point.timestamp.checked_sub(prev_point.timestamp) {
                price_point.relative_timestamp = Some(relative_time);
            }
        }

        if self.first_timestamp.is_none() {
            self.first_timestamp = Some(price_point.timestamp);
        }

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

        let prev_timestamp = if self.prices.len() >= 2 {
            Some(self.prices[self.prices.len() - 2].timestamp)
        } else {
            None
        };

        self.update_ema(price, price_point.timestamp, prev_timestamp);
        self.update_rsi();
        self.update_adx();
        self.update_stochastic();
        self.update_atr();
        self.update_atr_ma();
        self.update_bollinger_band();

        if timestamp.is_none() {
            self.make_confidence();
        }

        self.last_price = price;

        price_point
    }

    pub fn predict(&self, period: usize, strategy: TradingStrategy) -> Prediction {
        let mut price = self.prices[self.prices.len() - 1].price;

        let atr = self.atr(period);
        if atr == 0.0 {
            return Prediction {
                confidence: 0.0,
                price,
            };
        }
        let atr_ma = self.atr_ma(period);

        let mut confidence = self.metrics.confidence * atr / atr_ma;

        match strategy {
            TradingStrategy::TrendFollowLong => match confidence {
                x if x >= 0.5 => {
                    price += atr;
                }
                _ => {}
            },
            TradingStrategy::TrendFollowShort => match confidence {
                x if x <= -0.5 => {
                    price -= atr;
                }
                _ => {}
            },
            TradingStrategy::MeanReversionLong => match price {
                x if x < self.metrics.bollinger_band.lower => {
                    price = self.metrics.bollinger_band.center;
                    confidence = self.metrics.bollinger_band.lower / x - 0.5;
                }
                _ => {
                    confidence = 0.0;
                }
            },
            TradingStrategy::MeanReversionShort => match price {
                x if x > self.metrics.bollinger_band.upper => {
                    price = self.metrics.bollinger_band.center;
                    confidence = -x / self.metrics.bollinger_band.upper + 0.5;
                }
                _ => {
                    confidence = 0.0;
                }
            },
        }

        Prediction { confidence, price }
    }

    fn make_confidence(&mut self) {
        let price_confidence = self.metrics.price.crossover_effect();
        let rsi_confidence = self.metrics.rsi.crossover_effect();
        let adx_confidence = self.metrics.adx.crossover_effect();
        let stochastic_confidence = self.metrics.stochastic.crossover_effect();

        let confidence = price_confidence + rsi_confidence + adx_confidence + stochastic_confidence;

        fn color_confidence(confidence: f64) -> String {
            let color_code = if confidence > 0.0 {
                "\x1b[0;32m" // green
            } else if confidence < 0.0 {
                "\x1b[0;31m" // red
            } else {
                "\x1b[0;90m" // grey
            };

            format!("{}{:.3}\x1b[0m", color_code, confidence)
        }

        if confidence != 0.0 {
            log::debug!(
                "{}: price({}), rsi({}), adx({}), stochastic({})",
                color_confidence(confidence),
                color_confidence(price_confidence),
                color_confidence(rsi_confidence),
                color_confidence(adx_confidence),
                color_confidence(stochastic_confidence),
            );
        }

        self.metrics.confidence = confidence;
    }

    fn update_rsi(&mut self) {
        if let Some(rsi) = self.calculate_rsi(self.config.short_period) {
            self.metrics.rsi.short_term_val.update_value(rsi, None);
        }

        if let Some(rsi) = self.calculate_rsi(self.config.long_period) {
            self.metrics.rsi.long_term_val.update_value(rsi, None);
        }
    }

    fn update_adx(&mut self) {
        if let Some(adx) = self.calculate_adx(self.config.short_period) {
            self.metrics.adx.short_term_val.update_value(adx, None);
        }

        if let Some(adx) = self.calculate_adx(self.config.long_period) {
            self.metrics.adx.long_term_val.update_value(adx, None);
        }
    }

    fn update_stochastic(&mut self) {
        if let Some(stochastic) = self.calculate_stochastic(self.config.short_period) {
            self.metrics
                .stochastic
                .short_term_val
                .update_value(stochastic, None);
        }

        if let Some(stochastic) = self.calculate_stochastic(self.config.long_period) {
            self.metrics
                .stochastic
                .long_term_val
                .update_value(stochastic, None);
        }
    }

    fn update_ema(&mut self, price: f64, timestamp: i64, prev_timestamp: Option<i64>) {
        let time_difference = prev_timestamp.map_or(1.0, |prev| (timestamp - prev) as f64);
        self.metrics
            .price
            .short_term_val
            .update_value(price, Some(time_difference));
        self.metrics
            .price
            .long_term_val
            .update_value(price, Some(time_difference));
    }

    fn update_bollinger_band(&mut self) {
        if let Some(band) = self.calculate_bollinger_band(self.config.trading_period) {
            self.metrics.bollinger_band.update_value(&band);
        }
    }

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

        let sum: f64 = self
            .prices
            .iter()
            .skip(self.prices.len() - period)
            .map(|pp| pp.price)
            .sum();
        let mean = sum / period as f64;

        let variance_sum: f64 = self
            .prices
            .iter()
            .skip(self.prices.len() - period)
            .map(|pp| (pp.price - mean).powi(2))
            .sum();
        let variance = variance_sum / period as f64;
        let standard_deviation = variance.sqrt();

        Some(EdgeValue {
            upper: mean + 2.0 * standard_deviation,
            lower: mean - 2.0 * standard_deviation,
            center: mean,
        })
    }

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

        let mut gains = 0.0;
        let mut losses = 0.0;

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

        for window in recent_prices.windows(2) {
            if let [prev, current] = window {
                let change = current.price - prev.price;
                if change >= 0.0 {
                    gains += change;
                } else {
                    losses += change.abs();
                }
            }
        }
        let avg_gain = gains / period as f64;
        let avg_loss = losses / period as f64;

        let rs = if avg_loss != 0.0 {
            avg_gain / avg_loss
        } else {
            0.0
        };

        Some(1.0 - (1.0 / (1.0 + rs)))
    }

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

        let (high, low) = self.extract_high_low(period);
        let atr = (high - low) / period as f64;

        let mut plus_dm = 0.0;
        let mut minus_dm = 0.0;

        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 > 0.0 {
                plus_dm += up_move;
            }
            if down_move > up_move && down_move > 0.0 {
                minus_dm += down_move;
            }
        }

        let plus_di = plus_dm / atr;
        let minus_di = minus_dm / atr;
        Some((plus_di - minus_di).abs() / (plus_di + minus_di))
    }

    fn calculate_stochastic(&mut self, period: usize) -> Option<f64> {
        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_ma(&mut self) {
        let period = self.config.trading_period;
        let new_atr = self.metrics.atr.get(&period).copied();
        if let Some(new_atr) = new_atr {
            let atr_ma = self.metrics.atr_ma.entry(period).or_insert(new_atr);
            *atr_ma = (*atr_ma * (period - 1) as f64 + new_atr) / period as f64;
        }
    }

    fn update_atr(&mut self) {
        let period = self.config.trading_period;
        if self.prices.len() < period {
            return;
        }

        let atr = self.metrics.atr.get(&period);
        let mut tr_sum = 0.0;

        match atr {
            Some(atr) => {
                let alpha = 2.0 / (period as f64 + 1.0);
                let (current_high, current_low) = self.extract_high_low(self.prices.len());
                let tr = current_high - current_low;
                let new_atr = atr * (1.0 - alpha) + tr * alpha;
                self.metrics.atr.insert(period, new_atr);
            }
            None => {
                for _ in 0..(self.prices.len() - period + 1) {
                    let (high, low) = self.extract_high_low(period);
                    tr_sum += high - low;
                }

                let new_atr = tr_sum / (self.prices.len() - period + 1) as f64;
                self.metrics.atr.insert(period, new_atr);
            }
        }
    }

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