use std::env;

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

#[derive(Debug, Clone, Default)]
pub struct EdgeValue {
    upper: f64,
    lower: f64,
    center: f64,
    width: f64,
    width_trend: TrendValue,
    last_breakout_state: BreakOutType,
    breakout_duration_period_counter: u64,
    breakout_duration_period_max: u64,
}

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

impl BreakOutType {
    pub fn to_numeric(&self) -> f64 {
        match self {
            BreakOutType::UpBreakout => 0.55,
            BreakOutType::DownBreakout => 0.45,
            BreakOutType::None => 0.5,
        }
    }
}

lazy_static! {
    static ref BREAKOUT_EFFECTIVE_DURATION_PERIOD: u64 =
        env::var("BREAKOUT_EFFECTIVE_DURATION_PERIOD")
            .unwrap_or_else(|_| "180".to_string())
            .parse()
            .unwrap_or(180);
}

const CONSOLIDATION_FACTOR: f64 = 10.0;

impl EdgeValue {
    pub fn new(period: usize) -> Self {
        let width_trend = TrendValue::new(period);

        Self {
            upper: 0.0,
            lower: 0.0,
            center: 0.0,
            width: 0.0,
            width_trend,
            last_breakout_state: BreakOutType::None,
            breakout_duration_period_counter: 0,
            breakout_duration_period_max: *BREAKOUT_EFFECTIVE_DURATION_PERIOD,
        }
    }

    pub fn update_value(&mut self, upper: f64, lower: f64, center: f64) {
        self.upper = upper;
        self.lower = lower;
        self.center = center;
        self.width = upper - lower;
        self.width_trend.update_value(self.width);
    }

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

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

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

    #[allow(dead_code)]
    pub fn center(&self) -> f64 {
        self.center
    }

    pub fn is_breakout(&mut self, current_price: f64, previous_price: f64) -> BreakOutType {
        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::DownBreakout
        } else {
            BreakOutType::None
        };

        if self.breakout_duration_period_counter > 0 {
            if self.breakout_duration_period_counter > self.breakout_duration_period_max {
                self.breakout_duration_period_counter = 0;
            } else {
                return self.last_breakout_state.clone();
            }
        }

        if self.last_breakout_state != BreakOutType::None {
            self.breakout_duration_period_counter = 1;
            self.last_breakout_state = current_state.clone();
        }

        current_state
    }

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

    #[allow(dead_code)]
    pub fn is_approaching_upper(&self, price: f64) -> bool {
        (self.center + self.width_unit() < price)
            && (price < self.center + self.width_unit() * 2.0)
            && self.is_width_expanding()
    }

    #[allow(dead_code)]
    pub fn is_approaching_lower(&self, price: f64) -> bool {
        (self.center - self.width_unit() * 2.0 < price)
            && (price < self.center - self.width_unit())
            && self.is_width_expanding()
    }

    pub fn is_width_expanding(&self) -> bool {
        match self.width_trend.current_ema() {
            Some(width) => self.width > width,
            None => false,
        }
    }

    #[allow(dead_code)]
    fn width_unit(&self) -> f64 {
        self.width / CONSOLIDATION_FACTOR
    }
}
