use crate::{config::{Config}, HTTP_CLIENT};
use crate::lemmy::{Lemmy};
use crate::post_history::{SeriesHistory};
use chrono::{DateTime, Duration, Utc};
use std::sync::{Arc, RwLock};
use notify::{Event, EventKind, event::{AccessKind, AccessMode}, RecursiveMode, Watcher};
use tokio::time::sleep;
use systemd_journal_logger::connected_to_journal;

macro_rules! debug {
    ($msg:tt) => {
        match connected_to_journal() {
            true => log::debug!("[DEBUG] {}", $msg),
            false => println!("[DEBUG] {}", $msg),
        }
    };
}

macro_rules! info {
    ($msg:tt) => {
        match connected_to_journal() {
            true => log::info!("[INFO] {}", $msg),
            false => println!("[INFO] {}", $msg),
        }
    };
}

macro_rules! error {
    ($msg:tt) => {
        match connected_to_journal() {
            true => log::error!("[ERROR] {}", $msg),
            false => eprintln!("[ERROR] {}", $msg),
        }
    };
}

pub(crate) struct Bot {
    shared_config: Arc<RwLock<Config>>,
    history: SeriesHistory,
    run_start_time: DateTime<Utc>
}

enum Wait {
    Absolute,
    Buffer
}

impl Bot {
    pub(crate) fn new() -> Self {
        let config = Config::load();
        let shared_config: Arc<RwLock<Config>> = Arc::new(RwLock::new(config));

        let shared_config_copy = shared_config.clone();
        let mut watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
            match res {
                Ok(event) => {
                    if event.kind == EventKind::Access(AccessKind::Close(AccessMode::Write)) {
                        let mut write = shared_config_copy.write().expect("Write Lock Failed");
                        let new_config = Config::load();
                        write.series = new_config.series;
                        write.instance = new_config.instance;
                        write.protected_communities = new_config.protected_communities;
                        write.status_post_url = new_config.status_post_url;
                        info!("Reloaded Configuration");
                    }
                },
                Err(e) => {
                    let msg = format!("Error watching files: {e}");
                    error!(msg);
                }
            }
        }).expect("Watcher Error");

        watcher.watch(&Config::get_path(), RecursiveMode::NonRecursive).expect("Error in watcher");

        let history: SeriesHistory = SeriesHistory::load_history();

        Bot { shared_config, history, run_start_time: Utc::now() }
    }
    pub(crate) async fn run(&mut self) {
        loop {
            let mut lemmy = match Lemmy::new(&self.shared_config).await {
                Ok(data) => data,
                Err(_) => continue,
            };

            lemmy.get_communities().await;
            
            self.history = SeriesHistory::load_history();

            let start: DateTime<Utc> = Utc::now();
            while Utc::now() - start <= Duration::minutes(60) {
                self.run_start_time = Utc::now();
                self.ping_status().await;
                let read_copy = self.shared_config.read().expect("Read Lock Failed").clone();
                for series in read_copy.series {
                    series.update(&mut self.history, &lemmy, &self.shared_config).await;
                    debug!("Done Updating Series");
                    self.wait(1, Wait::Absolute).await;
                }
                debug!("Awaiting Timeout");
                self.wait(30, Wait::Buffer).await;
                debug!("Pinging Server");
                self.ping_status().await;
                debug!("Awaiting Timeout 2");
                self.wait(30, Wait::Absolute).await;
            }

            lemmy.logout().await;
        }
    }

    async fn ping_status(&self) {
        let read_config = &self.shared_config.read().expect("Read Lock Failed").clone();
        if let Some(status_url) = &read_config.status_post_url {
            match HTTP_CLIENT.get(status_url).send().await {
                Ok(_) => {},
                Err(e) => {
                    let err_msg = format!("While pinging status URL: {e}");
                    error!(err_msg);
                }
            }
        }
    }

    async fn wait(&self, seconds: i64, start_time: Wait) {
        let duration: Duration = Duration::seconds(seconds);
        let start_time: DateTime<Utc> = match start_time {
            Wait::Absolute => Utc::now(),
            Wait::Buffer => self.run_start_time,
        };
        while Utc::now() - start_time < duration {
            sleep(Duration::milliseconds(100).to_std().unwrap()).await
        }
    }
}