use std::{
    env,
    time::{Duration, SystemTime},
};

use crate::trend_value::TrendValue;
use lazy_static::lazy_static;

#[derive(Debug, Clone, Default)]
pub struct EdgeValue {
    pub upper: f64,
    pub lower: f64,
    pub center: f64,
    pub width: f64,
    pub width_trend: TrendValue,
    last_breakout_time: Option<SystemTime>,
    last_breakout_state: BreakOutType,
}

#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub enum BreakOutType {
    UpBreakout,
    DowndBreakout,
    #[default]
    None,
}

lazy_static! {
    static ref BREAKOUT_EFFECTIVE_DURATION_SECS: u64 = env::var("BREAKOUT_EFFECTIVE_DURATION_SECS")
        .unwrap_or_else(|_| "300".to_string()) // 300sec
        .parse()
        .unwrap_or(60);
}

const CONSOLIDATION_FACTOR: f64 = 10.0;

impl EdgeValue {
    pub fn new(upper: f64, lower: f64, center: f64, period: usize) -> Self {
        let width = upper - lower;
        let mut width_trend = TrendValue::new(period);
        width_trend.update_value(width);

        Self {
            upper,
            lower,
            center,
            width,
            width_trend,
            last_breakout_time: None,
            last_breakout_state: BreakOutType::None,
        }
    }

    pub fn is_breakout(&mut self, current_price: f64, previous_price: f64) -> BreakOutType {
        let now = SystemTime::now();
        let crossover_duration = Duration::from_secs(*BREAKOUT_EFFECTIVE_DURATION_SECS);

        let current_state = if previous_price <= self.upper && self.upper < current_price {
            BreakOutType::UpBreakout
        } else if current_price < self.lower && self.lower <= previous_price {
            BreakOutType::DowndBreakout
        } else {
            BreakOutType::None
        };

        if current_state == BreakOutType::None {
            if let Some(last_breakout_time) = self.last_breakout_time {
                if now.duration_since(last_breakout_time).unwrap_or_default() < crossover_duration {
                    return self.last_breakout_state.clone();
                }
            }
            return BreakOutType::None;
        }

        self.last_breakout_time = Some(now);
        self.last_breakout_state = current_state.clone();

        current_state
    }

    pub fn is_width_expanding(&self) -> bool {
        self.width > self.width_trend.current_ema()
    }

    pub fn is_tight_consolidation(&self, price: f64) -> bool {
        price > self.center - (self.width / CONSOLIDATION_FACTOR)
            && price < self.center + (self.width / CONSOLIDATION_FACTOR)
    }

    pub fn is_approaching_upper(&self, price: f64) -> bool {
        (self.upper - self.width / CONSOLIDATION_FACTOR < price) && (price < self.upper)
    }

    pub fn is_approaching_lower(&self, price: f64) -> bool {
        (self.lower < price) && (price < self.lower + self.width / CONSOLIDATION_FACTOR)
    }
}
