use super::{FilteredDataValue, HashMap};
use crate::value_estimation_team::datapoints::price_data::RealtimePriceData;
use futures::future::try_join_all;
use std::sync::Arc;
use tokio::sync::Mutex;

#[derive(PartialEq, Clone, Debug)]
pub enum SuperTrendArea {
    UP,
    DOWN,
}

#[derive(PartialEq, Clone, Debug)]
pub enum SuperTrendSignal {
    BUY,
    SELL,
}

#[derive(Clone, Debug)]
pub struct SupertrendData {
    pub band_value: f64,
    pub signal: Option<SuperTrendSignal>, // BUY or SELL
    pub area: SuperTrendArea,             // UP or DOWN
    pub close_time: i64,
}

impl SupertrendData {
    fn new() -> SupertrendData {
        let a = SupertrendData {
            band_value: 0.0,
            signal: None,
            area: SuperTrendArea::DOWN,
            close_time: 0,
        };

        a
    }
}

#[derive(Clone, Debug)]
struct TrueRangeData {
    tr_value: f64,
    close_time: i64,
}

impl TrueRangeData {
    fn new() -> TrueRangeData {
        let a = TrueRangeData {
            tr_value: 0.0,
            close_time: 0,
        };

        a
    }
}

#[derive(Clone, Debug)]
struct ATRData {
    atr_value: f64,
    close_time: i64,
}

impl ATRData {
    fn new() -> ATRData {
        let a = ATRData {
            atr_value: 0.0,
            close_time: 0,
        };

        a
    }
}

// TODO: should return type be Option?
// Implementation from TradingView Script (SuperTrend by KivancOzbilgic, source price: closeprice)
// return key: close_time
pub async fn supertrend(
    atr_period: usize,
    multiplier: f64,
    is_original_method: bool,
    input_rt_data: &HashMap<String, Vec<RealtimePriceData>>,
    filtered_symbols: &HashMap<String, FilteredDataValue>,
) -> Result<HashMap<String, Vec<SupertrendData>>, Box<dyn std::error::Error + Send + Sync>> {
    let mut supertrend_data_wrapper: HashMap<String, Vec<SupertrendData>> = HashMap::new();
    let mut supertrend_data_wrapper_arc = Arc::new(Mutex::new(supertrend_data_wrapper));

    let mut task_vec = Vec::new();
    for (symbol, filtered_data) in filtered_symbols {
        if let Some(rt_price_vec) = input_rt_data.get(symbol) {
            if atr_period < rt_price_vec.len() - 1 {
                let symbol_c = symbol.clone();
                let supertrend_data_wrapper_arc_c = Arc::clone(&supertrend_data_wrapper_arc);
                let rt_price_vec_c = rt_price_vec.clone();
                task_vec.push(tokio::spawn(async move {
                    // making True Range
                    let mut true_range_vec: Vec<TrueRangeData> = Vec::new();
                    let mut tr_data = TrueRangeData::new();
                    let mut tr_val_hl = 0.0; // High - Low
                    let mut tr_val_hcp = 0.0; // Abs(High - Close_previous)
                    let mut tr_val_lcp = 0.0; // Abs(Low - Close_previous)

                    let window_vec = rt_price_vec_c.windows(2);

                    tr_data.tr_value = rt_price_vec_c.first().unwrap().high_price
                        - rt_price_vec_c.first().unwrap().low_price;
                    tr_data.close_time = rt_price_vec_c.first().unwrap().close_time;
                    true_range_vec.push(tr_data.clone());

                    for buffer in window_vec {
                        tr_val_hl = buffer[1].high_price - buffer[1].low_price;
                        tr_val_hcp = (buffer[1].high_price - buffer[0].close_price).abs();
                        tr_val_lcp = (buffer[1].low_price - buffer[0].close_price).abs();

                        tr_data.tr_value = tr_val_hl.max(tr_val_hcp.max(tr_val_lcp));
                        tr_data.close_time = buffer[1].close_time;

                        true_range_vec.push(tr_data.clone());
                    }

                    // making Average True Range
                    let mut average_true_range_vec: Vec<ATRData> = Vec::new();
                    let mut atr_data = ATRData::new();

                    if is_original_method == true {
                        // original calculation of ATR
                        let mut first_value = 0.0;
                        let mut first_vec = &true_range_vec[0..atr_period];

                        for element in first_vec {
                            first_value += element.tr_value;
                        }
                        first_value /= (atr_period) as f64;
                        atr_data.atr_value = first_value;
                        atr_data.close_time = first_vec.last().unwrap().close_time;
                        average_true_range_vec.push(atr_data.clone());

                        let mut temp_prev_atr_value = first_value;
                        for element in &true_range_vec[atr_period..] {
                            atr_data.atr_value = ((temp_prev_atr_value
                                * ((atr_period - 1) as f64))
                                + element.tr_value)
                                / atr_period as f64;
                            atr_data.close_time = element.close_time;

                            average_true_range_vec.push(atr_data.clone());
                            temp_prev_atr_value = atr_data.atr_value;
                        }
                    } else {
                        // Calculation of ATR from SMA True Range with atr_period
                        let window = true_range_vec.windows(atr_period);

                        let mut sum = 0.0;
                        for buffer in window {
                            for element in buffer {
                                sum += element.tr_value;
                            }

                            atr_data.atr_value = sum / atr_period as f64;
                            atr_data.close_time = buffer.last().unwrap().close_time;
                            average_true_range_vec.push(atr_data.clone());

                            sum = 0.0;
                        }
                    }

                    // making Supertrend
                    #[derive(Clone)]
                    struct BandData {
                        basic_upperband: f64,
                        basic_lowerband: f64,
                        close_time: i64,
                    }

                    let mut supertrend_vec: Vec<SupertrendData> = Vec::new();
                    let mut supertrend_data = SupertrendData::new();
                    let mut band_vec: Vec<BandData> = Vec::new();
                    let mut band_data = BandData {
                        basic_upperband: 0.0,
                        basic_lowerband: 0.0,
                        close_time: 0,
                    };

                    let closetime_search_result = rt_price_vec_c.iter().position(|x| {
                        x.close_time == average_true_range_vec.first().unwrap().close_time
                    });
                    let mut rt_data = &rt_price_vec_c[closetime_search_result.unwrap()..];
                    let zipped = rt_data.iter().zip(average_true_range_vec);

                    for element in zipped {
                        band_data.basic_upperband = ((element.0.high_price + element.0.low_price)
                            / 2.0)
                            - (multiplier * element.1.atr_value);
                        band_data.basic_lowerband = ((element.0.high_price + element.0.low_price)
                            / 2.0)
                            + (multiplier * element.1.atr_value);
                        band_data.close_time = element.1.close_time;
                        band_vec.push(band_data.clone());
                    }

                    let mut zipped = rt_data.iter().zip(band_vec);
                    let first_element = zipped.next().unwrap();
                    let mut prev_close_price = first_element.0.close_price;
                    let mut final_upperband = 0.0;
                    let mut final_lowerband = 0.0;
                    let mut prev_final_upperband = first_element.1.basic_upperband;
                    let mut prev_final_lowerband = first_element.1.basic_lowerband;
                    let mut trend = 1; // 1 means up trend, -1 means down trend
                    let mut prev_trend = 1;
                    for element in zipped {
                        // set final upperband
                        if prev_close_price > prev_final_upperband {
                            final_upperband = prev_final_upperband.max(element.1.basic_upperband);
                        } else {
                            final_upperband = element.1.basic_upperband;
                        }

                        // set final lowerband
                        if prev_close_price < prev_final_lowerband {
                            final_lowerband = prev_final_lowerband.min(element.1.basic_lowerband);
                        } else {
                            final_lowerband = element.1.basic_lowerband;
                        }

                        // set supertrend
                        if trend == -1 && element.0.close_price > prev_final_lowerband {
                            supertrend_data.area = SuperTrendArea::UP;
                            trend = 1;
                        } else if trend == 1 && element.0.close_price < prev_final_upperband {
                            supertrend_data.area = SuperTrendArea::DOWN;
                            trend = -1;
                        }

                        if supertrend_data.area == SuperTrendArea::UP {
                            supertrend_data.band_value = final_upperband;
                        } else {
                            supertrend_data.band_value = final_lowerband;
                        }

                        if trend == 1 && prev_trend == -1 {
                            supertrend_data.signal = Some(SuperTrendSignal::BUY);
                        } else if trend == -1 && prev_trend == 1 {
                            supertrend_data.signal = Some(SuperTrendSignal::SELL);
                        } else {
                            supertrend_data.signal = None;
                        }

                        supertrend_data.close_time = element.1.close_time;
                        supertrend_vec.push(supertrend_data.clone());

                        prev_close_price = element.0.close_price;
                        prev_final_upperband = final_upperband;
                        prev_final_lowerband = final_lowerband;
                        prev_trend = trend;
                    }
                    let mut supertrend_data_wrapper_lock =
                        supertrend_data_wrapper_arc_c.lock().await;
                    supertrend_data_wrapper_lock.insert(symbol_c, supertrend_vec.clone());
                }));
            }
        }
    }
    try_join_all(task_vec).await?;
    let a = supertrend_data_wrapper_arc.lock().await.to_owned();
    Ok(a)
}
