use crate::{config::{Config, PostBody, SeriesConfig}, fetchers::{jnovel}, lemmy};
use crate::fetchers::jnovel::JPostInfo;
use crate::lemmy::{Lemmy, PostInfo};
use crate::post_history::SeriesHistory;
use chrono::{DateTime, Duration, Timelike, Utc};
use lemmy_api_common::post::CreatePost;
use lemmy_db_schema::newtypes::{CommunityId, LanguageId};
use lemmy_db_schema::PostFeatureType;
use std::collections::HashMap;
use tokio::time::sleep;
use crate::fetchers::Fetcher;
use systemd_journal_logger::connected_to_journal;

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

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

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

pub(crate) async fn run() {
    let mut last_reload: DateTime<Utc>;
    let mut lemmy: Lemmy;
    let mut login_error: bool;
    let mut communities: HashMap<String, CommunityId>;
    let mut post_history: SeriesHistory;
    let mut start: DateTime<Utc>;
    let mut config: Config = Config::load();
    last_reload = Utc::now();

    lemmy = match lemmy::login(&config).await {
        Ok(data) => data,
        Err(_) => panic!(),
    };
    login_error = false;

    communities = match lemmy.get_communities().await {
        Ok(data) => data,
        Err(_) => panic!(),
    };

    start = Utc::now();

    let info_msg = "Bot init successful, starting normal operations".to_owned();
    info!(info_msg);

    loop {
        idle(&start, &config).await;
        start = Utc::now();

        // replace with watcher
        if start - last_reload >= Duration::seconds(config.config_reload_seconds as i64) {
            config = Config::load();
            let message = "Config reloaded".to_owned();
            info!(message);
        }

        if login_error {
            let info_msg = "Login invalid, refreshing session";
            info!(info_msg);
            lemmy = match lemmy::login(&config).await {
                Ok(data) => data,
                Err(_) => continue,
            };
            login_error = false;
        }

        if start - last_reload >= Duration::seconds(config.config_reload_seconds as i64) {
            communities = match lemmy.get_communities().await {
                Ok(data) => data,
                Err(_) => {
                    login_error = true;
                    continue;
                }
            };
            let message = "Communities reloaded".to_owned();
            info!(message);
            last_reload = Utc::now();
        }

        post_history = SeriesHistory::load_history();

        let series = config.series.clone();
        for series in series {
            sleep(Duration::seconds(1).to_std().unwrap()).await;
            let info_msg = format!("Handling Series {}", series.slug);
            info!(info_msg);
            if handle_series(&series, &communities, &lemmy, &config, &mut post_history)
                .await
                .is_err()
            {
                login_error = true;
                continue;
            };
        }

        idle(&start, &config).await;
    }
}

async fn idle(start: &DateTime<Utc>, config: &Config) {
    let mut sleep_duration = Duration::seconds(30);
    let info_msg = format!("Idling for {} seconds", sleep_duration.num_seconds());
    info!(info_msg);
    if Utc::now() - start > sleep_duration {
        sleep_duration = Duration::seconds(60);
    }
    
    if let Some(status_url) = config.status_post_url.clone() {
        match reqwest::get(status_url).await {
            Ok(_) => {}
            Err(e) => {
                let err_msg = format!("{e}");
                error!(err_msg);
            }
        }
    };

    while Utc::now() - start < sleep_duration {
        sleep(Duration::milliseconds(100).to_std().unwrap()).await;
    }
}

async fn handle_series(series: &SeriesConfig, communities: &HashMap<String, CommunityId>, lemmy: &Lemmy, config: &Config, post_history: &mut SeriesHistory ) -> Result<(), ()> {
    let jnc = jnovel::JFetcherOptions::new(series.slug.clone(), series.parted);
    let post_list = match jnc.check_feed().await {
        Ok(data) => data,
        Err(_) => return Err(()),
    };
    
    if post_list.is_empty() && Utc::now().minute() % 10 == 0 {
        let info_msg = "No Updates found";
        info!(info_msg);
    }

    for post_info in post_list.clone().iter() {
        let post_part_info = post_info.get_part_info();
        let post_lemmy_info = post_info.get_info();

        if post_history.check_for_post(
            series.slug.as_str(),
            post_part_info.as_string().as_str(),
            post_lemmy_info.title.as_str(),
        ) {
            continue;
        }

        let post_series_config = match post_info {
            JPostInfo::Chapter { .. } => &series.prepub_community,
            JPostInfo::Volume { .. } => &series.volume_community,
        };

        let community_id = *communities
            .get(post_series_config.name.as_str())
            .expect("Given community is invalid");

        let post_body = match &post_series_config.post_body {
            PostBody::None => None,
            PostBody::Description => post_info.get_description(),
            PostBody::Custom(text) => Some(text.clone()),
        };

        let post_data = CreatePost {
            name: post_lemmy_info.title.clone(),
            community_id,
            url: Some(post_lemmy_info.url),
            body: post_body,
            honeypot: None,
            nsfw: None,
            language_id: Some(LanguageId(37)), // TODO get this id once every few hours per API request, the ordering of IDs suggests that the EN Id might change in the future
        };

        let info = format!(
            "Posting '{}' to {}",
            post_lemmy_info.title.as_str(),
            post_series_config.name.as_str()
        );
        info!(info);
        let post_id = lemmy.post(post_data).await?;

        if post_series_config.pin_settings.pin_new_post_community
            && config
            .protected_communities
            .contains(&post_series_config.name)
        {
            let info = format!(
                "Pinning '{}' to {}",
                post_lemmy_info.title,
                post_series_config.name.as_str()
            );
            info!(info);
            let pinned_posts = lemmy.get_community_pinned(community_id).await?;
            if !pinned_posts.is_empty() {
                let community_pinned_post = &pinned_posts[0];
                lemmy
                    .unpin(community_pinned_post.post.id, PostFeatureType::Community)
                    .await?;
            }
            lemmy.pin(post_id, PostFeatureType::Community).await?;
        } else if config
            .protected_communities
            .contains(&post_series_config.name)
        {
            let message = format!(
                "Community '{}' for Series '{}' is protected. Is this intended?",
                &post_series_config.name, series.slug
            );
            warn!(message);
        }

        if post_series_config.pin_settings.pin_new_post_local {
            let info = format!("Pinning '{}' to Instance", post_lemmy_info.title);
            info!(info);
            let pinned_posts = lemmy.get_local_pinned().await?;
            if !pinned_posts.is_empty() {
                for pinned_post in pinned_posts {
                    if config
                        .protected_communities
                        .contains(&pinned_post.community.name)
                    {
                        continue;
                    } else {
                        let community_pinned_post = &pinned_post;
                        lemmy
                            .unpin(community_pinned_post.post.id, PostFeatureType::Local)
                            .await?;
                        break;
                    }
                }
            }
            lemmy.pin(post_id, PostFeatureType::Local).await?;
        }

        let mut series_history = post_history.get_series(series.slug.as_str());
        let mut part_history = series_history.get_part(post_part_info.as_string().as_str());

        match post_info {
            JPostInfo::Chapter { .. } => part_history.chapter = post_info.get_info().title,
            JPostInfo::Volume { .. } => part_history.volume = post_info.get_info().title,
        }

        series_history.set_part(post_part_info.as_string().as_str(), part_history);
        post_history
            .set_series(series.slug.as_str(), series_history);
        post_history.save_history();
    }
    Ok(())
}