use super::FilteredDataValue;
use super::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(Debug, Clone, PartialEq)]
pub enum HeatMapLevel {
    ExtraHigh,
    High,
    Medium,
    Normal,
    Low,
}

#[derive(Debug, Clone)]
pub struct HeatmapVolumeData {
    pub heatmap_value: f64,
    pub heatmap_level: HeatMapLevel,
    pub close_time: i64,
}

// Implementation from TradingView Script (HeatMap Volume by xdecow)
pub async fn heatmap_volume(
    ma_len: usize,
    std_len: usize,
    extra_high_thold: f64,
    high_thold: f64,
    medium_thold: f64,
    normal_thold: f64,
    filtered_symbols: &HashMap<String, FilteredDataValue>,
    input_rt_data: &HashMap<String, Vec<RealtimePriceData>>,
) -> Result<HashMap<String, Vec<HeatmapVolumeData>>, Box<dyn std::error::Error + Send + Sync>> {
    let mut heatmap_data_wrapper: HashMap<String, Vec<HeatmapVolumeData>> = HashMap::new();
    let mut heatmap_data_wrapper_arc = Arc::new(Mutex::new(heatmap_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) {
            let heatmap_data_wrapper_arc_c = Arc::clone(&heatmap_data_wrapper_arc);
            let symbol_c = symbol.clone();
            let rt_price_vec_c = rt_price_vec.clone();
            task_vec.push(tokio::spawn(async move {
                if rt_price_vec_c.len() >= ma_len && rt_price_vec_c.len() >= std_len {
                    // calc mean
                    #[derive(Debug, Clone)]
                    struct MeanData {
                        mean_value: f64,
                        close_time: i64,
                    }

                    let mut mean_vec: Vec<MeanData> = Vec::new();
                    let mut mean_data = MeanData {
                        mean_value: 0.0,
                        close_time: 0,
                    };
                    let window = rt_price_vec_c.windows(ma_len);
                    for buffer in window {
                        // calculate SMA of volume
                        mean_data.mean_value = 0.0;
                        mean_data.close_time = 0;
                        for element in buffer {
                            mean_data.mean_value += element.quote_asset_volume;
                        }
                        mean_data.mean_value /= ma_len as f64;
                        mean_data.close_time = buffer.last().unwrap().close_time;

                        mean_vec.push(mean_data.clone());
                    }

                    // calc pstdev
                    #[derive(Debug, Clone)]
                    struct PstdevData {
                        pstdev_value: f64, // population standard deviation value
                        close_time: i64,
                    }
                    let mut pstdev_vec: Vec<PstdevData> = Vec::new();
                    let mut pstdev_data = PstdevData {
                        pstdev_value: 0.0,
                        close_time: 0,
                    };
                    let mut mean = 0.0;
                    let mut summation = 0.0;
                    let mut sample_minus_mean = 0.0;
                    let window = rt_price_vec_c.windows(std_len);
                    for buffer in window {
                        pstdev_data.pstdev_value = 0.0;
                        pstdev_data.close_time = 0;
                        mean = 0.0;
                        summation = 0.0;
                        sample_minus_mean = 0.0;
                        for element in buffer {
                            mean += element.quote_asset_volume;
                        }
                        mean /= std_len as f64;

                        for element in buffer {
                            sample_minus_mean = element.quote_asset_volume - mean;
                            summation += sample_minus_mean.powi(2);
                        }

                        pstdev_data.pstdev_value = (summation / std_len as f64).sqrt();
                        pstdev_data.close_time = buffer.last().unwrap().close_time;

                        pstdev_vec.push(pstdev_data.clone());
                    }

                    // calc stdbar and heatmap volume
                    let mut heatmap_vol_vec: Vec<HeatmapVolumeData> = Vec::new();
                    let mut heatmap_vol_data = HeatmapVolumeData {
                        heatmap_value: 0.0,
                        heatmap_level: HeatMapLevel::Normal,
                        close_time: 0,
                    };

                    if ma_len == std_len {
                        let mut rt_data_trunc_vec =
                            rt_price_vec_c.get(ma_len - 1..).unwrap().iter();

                        let zipped = mean_vec.iter().zip(pstdev_vec.iter());
                        for element in zipped {
                            heatmap_vol_data.heatmap_value =
                                (rt_data_trunc_vec.next().unwrap().quote_asset_volume
                                    - element.0.mean_value)
                                    / element.1.pstdev_value;
                            heatmap_vol_data.close_time = element.0.close_time;
                            if heatmap_vol_data.heatmap_value >= extra_high_thold {
                                heatmap_vol_data.heatmap_level = HeatMapLevel::ExtraHigh;
                            } else if heatmap_vol_data.heatmap_value > high_thold {
                                heatmap_vol_data.heatmap_level = HeatMapLevel::High;
                            } else if heatmap_vol_data.heatmap_value > medium_thold {
                                heatmap_vol_data.heatmap_level = HeatMapLevel::Medium;
                            } else if heatmap_vol_data.heatmap_value > normal_thold {
                                heatmap_vol_data.heatmap_level = HeatMapLevel::Normal;
                            } else {
                                heatmap_vol_data.heatmap_level = HeatMapLevel::Low;
                            }
                            heatmap_vol_vec.push(heatmap_vol_data.clone());
                        }
                    } else if ma_len > std_len {
                        let mut rt_data_trunc_vec =
                            rt_price_vec_c.get(std_len - 1..).unwrap().iter();
                        let mut mean_trunc_vec =
                            mean_vec.get(mean_vec.len() - std_len..).unwrap().iter();
                        let zipped = mean_trunc_vec.zip(pstdev_vec.iter());
                        for element in zipped {
                            heatmap_vol_data.heatmap_value =
                                (rt_data_trunc_vec.next().unwrap().quote_asset_volume
                                    - element.0.mean_value)
                                    / element.1.pstdev_value;
                            heatmap_vol_data.close_time = element.0.close_time;
                            heatmap_vol_vec.push(heatmap_vol_data.clone());
                        }
                    } else {
                        let mut rt_data_trunc_vec =
                            rt_price_vec_c.get(ma_len - 1..).unwrap().iter();
                        let mut pstdev_trunc_vec =
                            pstdev_vec.get(pstdev_vec.len() - ma_len..).unwrap().iter();
                        let zipped = mean_vec.iter().zip(pstdev_trunc_vec);
                        for element in zipped {
                            heatmap_vol_data.heatmap_value =
                                (rt_data_trunc_vec.next().unwrap().quote_asset_volume
                                    - element.0.mean_value)
                                    / element.1.pstdev_value;
                            heatmap_vol_data.close_time = element.0.close_time;
                            heatmap_vol_vec.push(heatmap_vol_data.clone());
                        } // level 구현
                    }
                    let mut heatmap_data_wrapper_lock = heatmap_data_wrapper_arc_c.lock().await;
                    heatmap_data_wrapper_lock.insert(symbol_c, heatmap_vol_vec.clone());
                }
            }));
        }
    }
    try_join_all(task_vec).await?;
    let a = heatmap_data_wrapper_arc.lock().await.to_owned();
    Ok(a)
}
