use crate::value_estimation_team::indicators::ema::{EmaData, ema};
use crate::value_estimation_team::datapoints::price_data::RealtimePriceData;
use super::FilteredData;
use std::sync::Arc;
use tokio::{fs::*, io::AsyncWriteExt, sync::Mutex, time::*};
use futures::future::try_join_all;

#[derive(Debug, Clone)]
pub struct MacdData {
    pub macd_value: f64,
    pub signal_value: f64,
    pub close_time: i64,
}

impl MacdData {
    fn new() -> MacdData {
        let a = MacdData {
            macd_value: 0.0,
            signal_value: 0.0,
            close_time: 0,
        };
        a
    }
}

#[derive(Debug, Clone)]
pub struct TempData {
    pub value: f64,
    pub close_time: i64,
}

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

pub async fn ema_macd(
    fast_len: usize,
    slow_len: usize,
    signal_smoothing: usize,
    input_rt_data: &Vec<(String, Vec<RealtimePriceData>)>,
    filtered_symbols: &Vec<FilteredData>
) -> Result<Vec<(String, Vec<MacdData>)>, Box<dyn std::error::Error + Send + Sync>> {
    let mut macd_oscil_vec: Vec<(String, Vec<MacdData>)> = Vec::new();

    let fast_emas = ema(fast_len, input_rt_data, filtered_symbols).await?;
    let slow_emas = ema(slow_len, input_rt_data, filtered_symbols).await?;
    let mut macd_data_wrapper: Vec<(String, Vec<MacdData>)> = Vec::new();
    let mut macd_data_wrapper_arc = Arc::new(Mutex::new(macd_data_wrapper));
    let mut task_vec = Vec::new();
    for filtered_elem in filtered_symbols {
        let fast_emas_c = fast_emas.clone();
        let slow_emas_c = slow_emas.clone();
        let filtered_elem_c = filtered_elem.clone();
        let macd_data_wrapper_arc_c = Arc::clone(&macd_data_wrapper_arc);
        task_vec.push(tokio::spawn(async move {
            let fast_search_result = fast_emas_c.iter().position(|x| x.0 == filtered_elem_c.symbol);
            let slow_search_result = slow_emas_c.iter().position(|x| x.0 == filtered_elem_c.symbol);

            if fast_search_result.is_some_and(|a| fast_emas_c[a].1.len() >= signal_smoothing) &&
            slow_search_result.is_some_and(|a| slow_emas_c[a].1.len() >= signal_smoothing) {
                let fast_ema = fast_emas_c[fast_search_result.unwrap()].1.clone();
                let slow_ema = slow_emas_c[fast_search_result.unwrap()].1.clone();
                let result = fast_ema.binary_search_by_key(
                    &slow_ema.first().unwrap().close_time,
                    |&EmaData {
                         ema_value,
                         close_time,
                     }| close_time,
                );
                if result.is_ok() {
                    // making MACD
                    let temp_vec = fast_ema.get(result.unwrap()..).unwrap();
                    let zipped = temp_vec.iter().zip(slow_ema);
                    let mut macd = TempData::new();
                    let mut macd_vec: Vec<TempData> = Vec::new();
                    for element in zipped {
                        macd.value = element.0.ema_value - element.1.ema_value;
                        macd.close_time = element.0.close_time;
                        macd_vec.push(macd.clone());
                    }
        
                    // making signal (smoothed MACD)
                    // TODO: this should be calculated in EMA (currently, SMA)
                    // let macd_vec_window = macd_vec.windows(signal_smoothing);
                    // let mut macd_signal_vec: Vec<TempData> = Vec::new();
                    // for window in macd_vec_window {
                    //     let mut sum_value = 0.0;
                    //     for element in window {
                    //         sum_value += element.value;
                    //     }
                    //     macd.value = sum_value / signal_smoothing as f64;
                    //     macd.close_time = window.last().unwrap().close_time;
                    //     macd_signal_vec.push(macd.clone());
                    // }
                    let mut macd_signal = TempData::new();
                    let mut macd_signal_vec: Vec<TempData> = Vec::new();
                    let partial_vec1 = macd_vec.get(..signal_smoothing).unwrap();
                    let partial_vec2 = macd_vec.get(signal_smoothing..).unwrap();

                    let mut sma_for_initial_value = 0.0;
                    for element in partial_vec1 {
                        sma_for_initial_value += element.value;
                    }
                    sma_for_initial_value /= signal_smoothing as f64;

                    macd_signal.value = sma_for_initial_value;
                    macd_signal.close_time = partial_vec1.last().unwrap().close_time;
                    macd_signal_vec.push(macd_signal.clone());

                    let alpha: f64 = 2.0 / (signal_smoothing as f64 + 1.0);
                    let mut ema_prev = sma_for_initial_value;
                    for element in partial_vec2 {
                        let ema_t = (1.0 - alpha) * ema_prev + alpha * element.value;

                        macd_signal.value = ema_t;
                        macd_signal.close_time = element.close_time;
                        macd_signal_vec.push(macd_signal.clone());

                        ema_prev = ema_t;
                    }
        
                    let result = macd_vec.binary_search_by_key(
                        &macd_signal_vec.first().unwrap().close_time,
                        |&TempData {
                             value,
                             close_time,
                         }| close_time,
                    );

                    if result.is_ok() {
                        let result = macd_vec.get(result.unwrap()..);
                        if result.is_some() {
                            let zipped = result.unwrap().iter().zip(macd_signal_vec);
                            
                            let mut macd_vec: Vec<MacdData> = Vec::new();
                            for element in zipped {
                                let mut macd = MacdData::new();
                                macd.macd_value = element.0.value;
                                macd.signal_value = element.1.value;
                                macd.close_time = element.0.close_time;
                                macd_vec.push(macd);
                            }
                            let mut macd_data_wrapper_lock = macd_data_wrapper_arc_c.lock().await;
                            macd_data_wrapper_lock.push((filtered_elem_c.symbol.clone(), macd_vec.clone()));
                        }
                    }
                }
            }
        }));
    }
    try_join_all(task_vec).await?;
    let a = macd_data_wrapper_arc.lock().await.to_owned();
    Ok(a)
}
