use crate::database_control::*;
use chrono::*;
use reqwest::{Client, ClientBuilder};
use serde_json::Value;
use sqlx::{Error, FromRow};
use tokio::{join, time::Duration, time::*};

const APP_ID_ACTIVE: &str = "7674e75976bd4f878a67f19926704433";
const APP_ID_TEMP: &str = "6bd6804c165d46dda5dd65500b17f3e8";

#[derive(Debug)]
struct ExchangeRateInfo {
    currency: String,
    rate: f64,
}

#[derive(Debug, FromRow)]
struct ExchangeRate {
    id: i64,
    date: NaiveDate,
    RUB: f64,
    CAD: f64,
    IQD: f64,
    CNY: f64,
    BRL: f64,
    IRR: f64,
    MXN: f64,
    NOK: f64,
}

pub async fn initialize_association_record() {
    let table_name = String::from("signal_association_opinion");
    let initial_columns = vec!["name", "is_working", "opinion", "weight"];
    let initial_values = vec![
        String::from("exchange_rate"),
        String::from("0"),
        String::from("-"),
        String::from("0.0"),
    ];
    insert_one_record(&table_name, &initial_columns, &initial_values).await;
}

pub async fn initialize_record() {
    // make date str
    let today = Utc::now().date();
    let mut prior_dates: Vec<Date<Utc>> = Vec::new();
    let mut prior_date = today.clone();

    for _ in 0..30 {
        prior_date = prior_date.pred();
        prior_dates.push(prior_date);
    }

    let mut today_str = String::new();
    today_str = today.to_string();
    today_str.pop();
    today_str.pop();
    today_str.pop();

    let mut prior_dates_str: Vec<String> = Vec::new();
    let mut temp_str = String::new();
    for element in prior_dates {
        temp_str = element.to_string();
        temp_str.pop();
        temp_str.pop();
        temp_str.pop();
        prior_dates_str.push(temp_str.clone());
    }
    prior_dates_str.reverse();

    let insert_table_name = String::from("foreign_exchange_rates");
    let insert_columns = vec![
        "date", "RUB", "CAD", "IQD", "CNY", "BRL", "IRR", "MXN", "NOK",
    ];
    let mut initial_values: Vec<String> = Vec::new();

    for element in prior_dates_str {
        let mut exchange_rates: Vec<ExchangeRateInfo> = Vec::new();

        loop {
            let result = reqeust_exchange_rate_historical(&element).await;
            match result {
                Ok(T) => {
                    exchange_rates = T;
                    break;
                }
                Err(E) => {
                    println!("{}", E);
                    sleep(Duration::from_secs(5)).await;
                }
            }
        }
        initial_values.clear();
        initial_values.push(element.clone());
        for element in exchange_rates {
            initial_values.push(element.rate.to_string());
        }
        insert_one_record(&insert_table_name, &insert_columns, &initial_values).await;
    }
}

pub async fn reqeust_exchange_rate_historical(
    date: &String,
) -> Result<Vec<ExchangeRateInfo>, Box<dyn std::error::Error + Send + Sync>> {
    let client = ClientBuilder::new()
        .timeout(tokio::time::Duration::from_secs(30))
        .build()?;
    let mut url = String::from("https://openexchangerates.org/api/historical/");
    url.push_str(&date);
    url.push_str(".json?app_id=");
    url.push_str(APP_ID_TEMP);

    let res = client.get(&url).send().await?;

    let body = res.text_with_charset("utf-8").await?;

    let v = serde_json::from_str::<Value>(body.as_str())?;
    let mut exchange_rates: Vec<ExchangeRateInfo> = Vec::new();
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("RUB"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("CAD"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("IQD"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("CNY"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("BRL"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("IRR"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("MXN"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("NOK"),
        rate: 0.0,
    });

    for element in &mut exchange_rates {
        match element.currency.as_str() {
            "RUB" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("RUB")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "CAD" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("CAD")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "IQD" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("IQD")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "CNY" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("CNY")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "BRL" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("BRL")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "IRR" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("IRR")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "MXN" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("MXN")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "NOK" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("NOK")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            _ => {}
        }
    }
    Ok(exchange_rates)
}

pub async fn reqeust_exchange_rate_latest(
) -> Result<Vec<ExchangeRateInfo>, Box<dyn std::error::Error + Send + Sync>> {
    let client = ClientBuilder::new()
        .timeout(tokio::time::Duration::from_secs(30))
        .build()?;
    let mut url = String::from("https://openexchangerates.org/api/latest.json?app_id=");
    url.push_str(APP_ID_ACTIVE);

    let res = client.get(&url).send().await.unwrap();

    let body = res.text_with_charset("utf-8").await?;

    let v = serde_json::from_str::<Value>(body.as_str())?;
    let mut exchange_rates: Vec<ExchangeRateInfo> = Vec::new();
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("RUB"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("CAD"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("IQD"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("CNY"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("BRL"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("IRR"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("MXN"),
        rate: 0.0,
    });
    exchange_rates.push(ExchangeRateInfo {
        currency: String::from("NOK"),
        rate: 0.0,
    });

    for element in &mut exchange_rates {
        match element.currency.as_str() {
            "RUB" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("RUB")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "CAD" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("CAD")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "IQD" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("IQD")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "CNY" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("CNY")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "BRL" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("BRL")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "IRR" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("IRR")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "MXN" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("MXN")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            "NOK" => {
                element.rate = v
                    .get("rates")
                    .unwrap()
                    .get("NOK")
                    .unwrap()
                    .as_f64()
                    .unwrap();
            }
            _ => {}
        }
    }
    Ok(exchange_rates)
}

// Weight to average is from amount of crude oil for each country: https://yearbook.enerdata.co.kr/crude-oil/world-production-statistics.html
// based on 2020 data
// Weight: USD(1) , RUB(0.709141), CAD(0.353186), IQD(0.285319), CNY(0.278393), BRL(0.216066), IRR(0.184211),  MXN(0.131579), NOK(0.131579)
// FX rate from: https://openexchangerates.org
pub async fn monitoring_fx_rate_index() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let mut exchange_rate_vec = select_fx_rate().await;
    let mut today = Utc::now().date().to_string();
    let mut yesterday = Utc::now().date().pred().to_string();
    today.pop();
    today.pop();
    today.pop();
    yesterday.pop();
    yesterday.pop();
    yesterday.pop();

    exchange_rate_vec.sort_by_key(|a| a.id);
    let mut weight_avg_vec: Vec<f64> = Vec::new();
    if exchange_rate_vec.last().unwrap().date.to_string() == yesterday {
        let rub_weight = 0.709141;
        let cad_weight = 0.353186;
        let iqd_weight = 0.285319;
        let cny_weight = 0.278393;
        let brl_weight = 0.216066;
        let irr_weight = 0.184211;
        let mxn_weight = 0.131579;
        let nok_weight = 0.131579;

        // calculate each average for 30 days
        let mut rub_avg = 0.0;
        let mut cad_avg = 0.0;
        let mut iqd_avg = 0.0;
        let mut cny_avg = 0.0;
        let mut brl_avg = 0.0;
        let mut irr_avg = 0.0;
        let mut mxn_avg = 0.0;
        let mut nok_avg = 0.0;

        for element in &exchange_rate_vec {
            rub_avg += element.RUB;
            cad_avg += element.CAD;
            iqd_avg += element.IQD;
            cny_avg += element.CNY;
            brl_avg += element.BRL;
            irr_avg += element.IRR;
            mxn_avg += element.MXN;
            nok_avg += element.NOK;
        }
        rub_avg /= exchange_rate_vec.len() as f64;
        cad_avg /= exchange_rate_vec.len() as f64;
        iqd_avg /= exchange_rate_vec.len() as f64;
        cny_avg /= exchange_rate_vec.len() as f64;
        brl_avg /= exchange_rate_vec.len() as f64;
        irr_avg /= exchange_rate_vec.len() as f64;
        mxn_avg /= exchange_rate_vec.len() as f64;
        nok_avg /= exchange_rate_vec.len() as f64;

        // calculate weight average for each day
        for element in &exchange_rate_vec {
            let weight_avg = ((element.RUB / rub_avg * rub_weight)
                + (element.CAD / cad_avg * cad_weight)
                + (element.IQD / iqd_avg * iqd_weight)
                + (element.CNY / cny_avg * cny_weight)
                + (element.BRL / brl_avg * brl_weight)
                + (element.IRR / irr_avg * irr_weight)
                + (element.MXN / mxn_avg * mxn_weight)
                + (element.NOK / nok_avg * nok_weight))
                / (rub_weight
                    + cad_weight
                    + iqd_weight
                    + cny_weight
                    + brl_weight
                    + irr_weight
                    + mxn_weight
                    + nok_weight);
            weight_avg_vec.push(weight_avg);
        }

        let mut current_exchange_rates: Vec<ExchangeRateInfo> = Vec::new();
        loop {
            let result = reqeust_exchange_rate_latest().await;
            match result {
                Ok(T) => {
                    current_exchange_rates = T;
                    break;
                }
                Err(E) => {
                    println!("{}", E);
                    let association_update_table_name = String::from("signal_association_opinion");
                    let association_update_condition =
                        vec![(String::from("name"), String::from("exchange_rate"))];
                    let association_update_values: Vec<(String, String)> = vec![
                        (String::from("is_working"), 0.to_string()),
                        (String::from("opinion"), String::from("-")),
                    ];
                    update_record2(
                        &association_update_table_name,
                        &association_update_values,
                        &association_update_condition,
                    )
                    .await;
                    sleep(Duration::from_secs(60)).await;
                }
            }
        }

        let mut current_avg = 0.0;

        for element in &current_exchange_rates {
            match element.currency.as_str() {
                "RUB" => {
                    current_avg += element.rate / rub_avg * rub_weight;
                }
                "CAD" => {
                    current_avg += element.rate / cad_avg * cad_weight;
                }
                "IQD" => {
                    current_avg += element.rate / iqd_avg * iqd_weight;
                }
                "CNY" => {
                    current_avg += element.rate / cny_avg * cny_weight;
                }
                "BRL" => {
                    current_avg += element.rate / brl_avg * brl_weight;
                }
                "IRR" => {
                    current_avg += element.rate / irr_avg * irr_weight;
                }
                "MXN" => {
                    current_avg += element.rate / mxn_avg * mxn_weight;
                }
                "NOK" => {
                    current_avg += element.rate / nok_avg * nok_weight;
                }
                _ => {}
            }
        }
        current_avg /= (rub_weight
            + cad_weight
            + iqd_weight
            + cny_weight
            + brl_weight
            + irr_weight
            + mxn_weight
            + nok_weight);

        // update signal_association_opinion
        let association_update_table_name = String::from("signal_association_opinion");
        let association_update_condition =
            vec![(String::from("name"), String::from("exchange_rate"))];
        let mut association_update_values: Vec<(String, String)> = Vec::new();
        if current_avg > 1.003 {
            // UP Trend
            association_update_values = vec![
                (String::from("is_working"), 1.to_string()),
                (String::from("opinion"), String::from("UP")),
            ];
        } else if current_avg < 0.997 {
            // DOWN Trend
            association_update_values = vec![
                (String::from("is_working"), 1.to_string()),
                (String::from("opinion"), String::from("DOWN")),
            ];
        } else {
            // KEEP Trend
            association_update_values = vec![
                (String::from("is_working"), 1.to_string()),
                (String::from("opinion"), String::from("KEEP")),
            ];
        }
        update_record2(
            &association_update_table_name,
            &association_update_values,
            &association_update_condition,
        )
        .await;
    } else {
        // delete the first record in [foreign_exchange_rates] and
        let table_name = String::from("foreign_exchange_rates");
        let mut delete_condition = String::from("WHERE id = ");
        delete_condition.push_str(exchange_rate_vec.first().unwrap().id.to_string().as_str());
        delete_record(&table_name, &delete_condition).await;

        // add yesterday's record
        let mut exchange_rates: Vec<ExchangeRateInfo> = Vec::new();

        loop {
            let result = reqeust_exchange_rate_historical(&yesterday).await;
            match result {
                Ok(T) => {
                    exchange_rates = T;
                    break;
                }
                Err(E) => {
                    println!("{}", E);
                    let association_update_table_name = String::from("signal_association_opinion");
                    let association_update_condition =
                        vec![(String::from("name"), String::from("exchange_rate"))];
                    let association_update_values: Vec<(String, String)> = vec![
                        (String::from("is_working"), 0.to_string()),
                        (String::from("opinion"), String::from("-")),
                    ];
                    sleep(Duration::from_secs(60)).await;
                }
            }
        }
        let insert_columns = vec![
            "date", "RUB", "CAD", "IQD", "CNY", "BRL", "IRR", "MXN", "NOK",
        ];

        let mut initial_values: Vec<String> = Vec::new();
        initial_values.push(yesterday.clone());
        for element in exchange_rates {
            initial_values.push(element.rate.to_string());
        }

        insert_one_record(&table_name, &insert_columns, &initial_values).await;
    }

    Ok(())
}

async fn select_fx_rate() -> Vec<ExchangeRate> {
    let select_table_name = String::from("foreign_exchange_rates");
    let select_columns = String::from("*");
    let select_condition = None;
    let select_struct = ExchangeRate {
        id: 0,
        date: chrono::naive::MAX_DATE,
        RUB: 0.0,
        CAD: 0.0,
        IQD: 0.0,
        CNY: 0.0,
        BRL: 0.0,
        IRR: 0.0,
        MXN: 0.0,
        NOK: 0.0,
    };

    let exchange_rate_vec = select_record(
        &select_table_name,
        &select_columns,
        &select_condition,
        &select_struct,
    )
    .await
    .unwrap();

    exchange_rate_vec
}
