use crate::{
    edge_value::EdgeValue,
    trade_chance::TradeDetail,
    trend_metrics::{CrossOverType, TrendMetrics, TrendType},
    PricePoint, TradeAction,
};
use std::{cell::RefCell, collections::VecDeque, vec};

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

impl MarketCondition {
    pub fn to_numeric(&self) -> f64 {
        match self {
            MarketCondition::ColdMarket => 1.15,
            MarketCondition::UpBreak => 1.075,
            MarketCondition::UpInit => 1.05,
            MarketCondition::Unknown => 1.0,
            MarketCondition::DownInit => 0.95,
            MarketCondition::DownBreak => 0.925,
            MarketCondition::HotMarket => 0.85,
            MarketCondition::RangeBound => 0.99,
        }
    }
}

#[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, Default)]
pub struct GridConfig {
    max_grid_size: usize,
    min_tick_price: f64,
    grid_bias_factor: usize,
}

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

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

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

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

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

        let grid_config = GridConfig {
            max_grid_size,
            grid_bias_factor,
            min_tick_price,
        };

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

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

    pub fn last_price(&self) -> f64 {
        self.last_price
    }

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

    pub fn adx(&self) -> f64 {
        match self.metrics.adx.current_value {
            Some(v) => v,
            None => 0.5,
        }
    }

    pub fn rsi(&self) -> f64 {
        match self.metrics.rsi.borrow().current_value {
            Some(v) => v,
            None => 0.5,
        }
    }

    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 price_point = PricePoint::new(
            price,
            timestamp,
            self.metrics
                .price
                .borrow()
                .short_term_val
                .current_ema()
                .unwrap_or_default(),
            self.metrics
                .price
                .borrow()
                .long_term_val
                .current_ema()
                .unwrap_or_default(),
            self.metrics.rsi.borrow().current_value.unwrap_or(0.5),
        );

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

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

        self.last_price = price;

        price_point
    }

    pub fn get_market_detail(&self) -> (f64, f64) {
        let is_crossover = self
            .metrics
            .price
            .borrow_mut()
            .is_crossover()
            .unwrap_or(CrossOverType::None)
            .to_numeric();

        let spread = self.calculate_spread();

        return (is_crossover, spread.unwrap_or_default());
    }

    pub fn assess_market_condition(&self) -> MarketCondition {
        let new_condition = self.assess_market_condition_sub();
        *self.metrics.market_condition.borrow_mut() = new_condition.clone();
        new_condition
    }

    fn assess_market_condition_sub(&self) -> MarketCondition {
        MarketCondition::Unknown
    }

    fn calculate_buy_bias(_position_weight: f64) -> f64 {
        0.5
    }

    fn calculate_spread(&self) -> Option<f64> {
        self.metrics
            .atr
            .map(|v| v / self.grid_config.max_grid_size as f64)
    }

    fn signal_grid_trade(
        &self,
        _open_price: Option<f64>,
        position_weight: f64,
    ) -> Vec<TradeAction> {
        let mut actions = vec![];
        let confidence = 1.0;

        let buy_bias = (self.grid_config.max_grid_size as f64
            * Self::calculate_buy_bias(position_weight))
        .round() as i32;

        let sell_bias = self.grid_config.max_grid_size as i32 - buy_bias;

        let price_step = match self.calculate_spread() {
            Some(v) => v,
            None => return actions,
        };

        let is_crossover = match self.metrics.price.borrow_mut().is_crossover() {
            Ok(v) => v,
            Err(_) => return actions,
        };

        let buy_price_step;
        let sell_price_step;
        let base_price = match is_crossover {
            CrossOverType::GoldenCross => {
                buy_price_step = self.grid_config.min_tick_price;
                sell_price_step = price_step;
                self.last_price + buy_price_step * buy_bias as f64
            }
            CrossOverType::DeadCross => {
                buy_price_step = price_step;
                sell_price_step = self.grid_config.min_tick_price;
                self.last_price - sell_price_step * buy_bias as f64
            }
            CrossOverType::None => return actions,
        };

        for i in 1..=buy_bias {
            let buy_price = base_price - buy_price_step * i as f64;
            actions.push(TradeAction::BuyOpen(TradeDetail::new(
                buy_price, confidence,
            )));
        }

        for i in 1..=sell_bias {
            let sell_price = base_price + sell_price_step * i as f64;
            actions.push(TradeAction::SellOpen(TradeDetail::new(
                sell_price, confidence,
            )));
        }

        actions
    }

    pub fn is_open_signaled(
        &self,
        strategy: TradingStrategy,
        open_price: Option<f64>,
        position_weight: f64,
    ) -> Vec<TradeAction> {
        self.log_price_info();

        let action = match strategy {
            TradingStrategy::TrendFollow => {
                let atr = self.atr();
                if atr == 0.0 {
                    return vec![];
                }

                let condition = self.assess_market_condition();
                let confidence = 1.0;

                match condition {
                    MarketCondition::UpBreak => {
                        TradeAction::BuyOpen(TradeDetail::new(self.last_price + atr, confidence))
                    }
                    MarketCondition::UpInit => {
                        TradeAction::BuyOpen(TradeDetail::new(self.last_price + atr, confidence))
                    }
                    MarketCondition::DownInit => {
                        TradeAction::SellOpen(TradeDetail::new(self.last_price - atr, confidence))
                    }
                    MarketCondition::DownBreak => {
                        TradeAction::SellOpen(TradeDetail::new(self.last_price - atr, confidence))
                    }
                    MarketCondition::HotMarket => {
                        TradeAction::SellOpen(TradeDetail::new(self.last_price - atr, confidence))
                    }
                    MarketCondition::ColdMarket => {
                        TradeAction::BuyOpen(TradeDetail::new(self.last_price + atr, confidence))
                    }
                    _ => {
                        return vec![];
                    }
                }
            }
            TradingStrategy::RangeGrid => {
                return self.signal_grid_trade(open_price, position_weight)
            }
            _ => return vec![],
        };

        vec![action]
    }

    pub fn is_close_signaled(&self, _strategy: TradingStrategy) -> TradeAction {
        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
                .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: f64) {
        self.metrics.price.borrow_mut().update(price, price, price);
    }

    fn update_bollinger_band(&mut self) {
        if let Some((upper, lower, center)) =
            self.calculate_bollinger_band(self.config.trading_period)
        {
            // band.width_trend.update_value(band.width);
            self.metrics
                .bollinger_band
                .borrow_mut()
                .update_value(upper, lower, center);
        }
    }

    fn calculate_bollinger_band(&self, period: usize) -> Option<(f64, f64, f64)> {
        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((
            mean + 2.0 * standard_deviation,
            mean - 2.0 * standard_deviation,
            mean,
        ))
    }

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

        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(window_size) {
            if let (Some(first), Some(last)) = (window.first(), window.last()) {
                let change = last.price - first.price;
                if change >= 0.0 {
                    gains += change;
                } else {
                    losses += change.abs();
                }
            }
        }

        let rs = if losses != 0.0 { gains / losses } else { 100.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;

        if atr == 0.0 {
            return None;
        }

        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;

        let denominator = plus_di + minus_di;
        if denominator == 0.0 {
            return None;
        }

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

    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(&mut self) {
        let period = self.config.trading_period;
        if self.prices.len() <= period {
            // +1 is to included the previos_close price
            return;
        }

        if self.metrics.atr.is_none() {
            let mut tr_sum = 0.0;
            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()
                .cloned()
                .fold(f64::NAN, f64::max);

                tr_sum += tr;
            }

            let initial_atr = tr_sum / (self.prices.len() - period + 1) as f64;
            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()
            .cloned()
            .fold(f64::NAN, f64::max);

            let alpha = 2.0 / (period as f64 + 1.0);
            let new_atr = self.metrics.atr.unwrap() * (1.0 - alpha) + current_tr * alpha;
            self.metrics.atr = Some(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))
            })
    }

    fn log_price_info(&self) {
        const _GREEN: &str = "\x1b[0;32m";
        const LIGHT_GREEN: &str = "\x1b[0;92m";
        const RED: &str = "\x1b[0;31m";
        const _ORANGE: &str = "\x1b[0;33m";
        const RESET: &str = "\x1b[0m";
        const GREY: &str = "\x1b[0;90m";

        let color1 = match self.metrics.price.borrow().trend_type().unwrap_or_default() {
            TrendType::StrongUp => LIGHT_GREEN,
            TrendType::WeakUp => _GREEN,
            TrendType::WeakDown => _ORANGE,
            TrendType::StrongDown => RED,
            _ => GREY,
        };

        let color2 = match self.metrics.bollinger_band.borrow().is_width_expanding() {
            true => LIGHT_GREEN,
            false => RED,
        };

        let price = self.last_price;
        let upper = self.metrics.bollinger_band.borrow().upper();
        let lower = self.metrics.bollinger_band.borrow().lower();
        let rsi = self.metrics.rsi.borrow().current_value.unwrap_or(0.5);
        let atr = self.atr();
        let width = self.metrics.bollinger_band.borrow().width();
        let condition = self.assess_market_condition();
        let ema_diff = match self.metrics.price.borrow().ema_difference_rate() {
            Some(diff) => diff * 100.0,
            None => 0.0,
        };
        let short_term_change_rate = self
            .metrics
            .price
            .borrow()
            .short_term_val
            .ema_change_rate()
            .unwrap_or_default()
            * 10000.0;
        let long_term_change_rate = self
            .metrics
            .price
            .borrow()
            .long_term_val
            .ema_change_rate()
            .unwrap_or_default()
            * 10000.0;

        if price <= lower {
            log::debug!(
                "{:<15?}: {}{:<.1}({:<.2}, {:<.3}, {:<.3}){} - {:<.1} - {:<.1} rsi({:<.2}) atr({:.1}) {}width({:<.1})",
                condition,
                color1,
                price,
                ema_diff,
                short_term_change_rate,
                long_term_change_rate,
                RESET,
                lower,
                upper,
                rsi,
                atr,
                color2,
                width,
            );
        } else if upper >= price && price >= lower {
            log::debug!(
                "{:<15?}: {:<.1} - {}{:<.1}({:<.2}, {:<.3}, {:<.3}){} - {:<.1} rsi({:<.2}) atr({:.1}) {}width({:<.1})",
                condition,
                lower,
                color1,
                price,
                ema_diff,
                short_term_change_rate,
                long_term_change_rate,
                RESET,
                upper,
                rsi,
                atr,
                color2,
                width,
            );
        } else {
            log::debug!(
                "{:<15?}: {:<.1} - {:<.1} - {}{:<.1}({:<.2}, {:<.3}, {:<.3}){} rsi({:<.2}) atr({:.1}) {}width({:<.1})",
                condition,
                lower,
                upper,
                color1,
                price,
                ema_diff,
                short_term_change_rate,
                long_term_change_rate,
                RESET,
                rsi,
                atr,
                color2,
                width,
            );
        }
    }
}
