use crate::database_control::*;
use chrono::TimeZone;
use reqwest::{Client, ClientBuilder};
use serde::Deserialize;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::{join, time::*};

// structure for checking server time and local time
#[derive(Debug)]
pub struct UserTime {
    pub server_epoch: u128,    // server time in Unix Epoch Time
    pub local_epoch: u128,     // local time where the tradingbot is running in Unix Epoch Time.
    pub epoch_difference: i64, // epoch difference from server time to local time in Unix Epoch Time. (can be used as RTT)
    // positive value means server is leading to local, negative value means server is lagging to local.
    server_ymdhs: String, // human-readable date form of server time in UTC (y: year, m: month, d: day, h: hour, s: second)
    local_ymdhs: String, // human-readable date form of local time in UTC (y: year, m: month, d: day, h: hour, s: second)
    last_server_epoch: u128, // the last server epoch until the server is turned off.
    last_server_ymdhs: String, // the last server date until the server is turned off.
}

impl UserTime {
    // make an instance and initialize fields
    pub fn new() -> UserTime {
        UserTime {
            server_epoch: 0,
            local_epoch: 0,
            epoch_difference: 0,
            server_ymdhs: String::new(),
            local_ymdhs: String::new(),
            last_server_epoch: 0,
            last_server_ymdhs: String::from("----"),
        }
    }

    // update all field in Time struct.
    async fn update(
        &mut self,
        client: &Client,
    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        let (servertime, localtime) = join!(Self::get_server_time(&client), Self::get_local_time());
        self.server_epoch = servertime?;
        self.local_epoch = localtime?;
        self.epoch_difference =
            Self::get_difference_time(self.server_epoch, self.local_epoch).await;
        self.server_ymdhs = Self::convert_epoch_to_ymdhs(self.server_epoch);
        self.local_ymdhs = Self::convert_epoch_to_ymdhs(self.local_epoch);
        Ok(())
    }

    // to record the last server-on time when request doesn't work.
    pub async fn update_last_server(&mut self) {
        self.last_server_epoch = self.server_epoch;
        if self.server_epoch == 0 {
            self.last_server_ymdhs =
                String::from("server has been shutdown before the bot was executed.");
        } else {
            self.last_server_ymdhs = self.server_ymdhs.clone();
        }
    }

    async fn get_server_time(
        client: &Client,
    ) -> Result<u128, Box<dyn std::error::Error + Send + Sync>> {
        #[derive(Deserialize)]
        struct ServerTime {
            serverTime: u128,
        }
        let mut servertime = ServerTime { serverTime: 0 };

        let response = client
            .get("https://api.binance.com/api/v3/time")
            .send()
            .await?;

        servertime = response.json::<ServerTime>().await?;
        Ok(servertime.serverTime)
    }

    // function to get the current local time
    async fn get_local_time() -> Result<u128, std::time::SystemTimeError> {
        match SystemTime::now().duration_since(UNIX_EPOCH) {
            Ok(T) => Ok(T.as_millis()),
            Err(E) => Err(E),
        }
    }

    // function to get the difference time from the local to the server
    async fn get_difference_time(servertime: u128, localtime: u128) -> i64 {
        if servertime >= localtime {
            (servertime - localtime) as i64
        } else {
            0 - ((localtime - servertime) as i64)
        }
    }

    // function to convert Unix Epoch time of server to human-readable date in UTC
    fn convert_epoch_to_ymdhs(epoch_time: u128) -> String {
        let time_i64 = (epoch_time / 1000) as i64;
        chrono::offset::Utc.timestamp(time_i64, 0).to_string()
    }
}

// run time checking and store data into database.
pub async fn execute_time_check(
    usertime: &mut UserTime,
    client: &Client,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    usertime.update(&client).await?;

    let table_name = String::from("time");
    let mut record: Vec<(&str, &str)> = Vec::new();
    let condition = vec![("id", "1")];

    let server_epoch = usertime.server_epoch.to_string();
    let local_epoch = usertime.local_epoch.to_string();
    let epoch_difference = usertime.epoch_difference.to_string();
    let last_server_epoch = usertime.last_server_epoch.to_string();

    record.push(("server_epoch", server_epoch.as_str()));
    record.push(("local_epoch", local_epoch.as_str()));
    record.push(("epoch_difference", epoch_difference.as_str()));
    record.push(("server_ymdhs", usertime.server_ymdhs.as_str()));
    record.push(("local_ymdhs", usertime.local_ymdhs.as_str()));
    record.push(("last_server_epoch", last_server_epoch.as_str()));
    record.push(("last_server_ymdhs", usertime.last_server_ymdhs.as_str()));

    update_record(&table_name, &record, &condition).await?;
    // println!("time check 완료");
    Ok(())
}

impl Clone for UserTime {
    fn clone(&self) -> Self {
        UserTime {
            server_epoch: self.server_epoch,
            local_epoch: self.local_epoch,
            epoch_difference: self.epoch_difference,
            server_ymdhs: self.last_server_ymdhs.clone(),
            local_ymdhs: self.local_ymdhs.clone(),
            last_server_epoch: self.last_server_epoch,
            last_server_ymdhs: self.last_server_ymdhs.clone(),
        }
    }
}
