use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use chrono::{Timelike, Utc};
use crate::config::PostBody::Description;
use lemmy_api_common::sensitive::Sensitive;
use lemmy_db_schema::PostFeatureType;
use serde_derive::{Deserialize, Serialize};
use crate::lemmy::{Lemmy, PartInfo, PostType};
use crate::post_history::{SeriesHistory};
use systemd_journal_logger::connected_to_journal;
use crate::fetchers::{FetcherTrait, Fetcher};
use crate::fetchers::jnovel::{JNovelFetcher};

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),
        }
    };
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub(crate) struct Config {
    pub(crate) instance: String,
    username: String,
    password: String,
    pub(crate) status_post_url: Option<String>,
    pub(crate) config_reload_seconds: u32,
    pub(crate) protected_communities: Vec<String>,
    pub(crate) series: Vec<SeriesConfig>,
}

impl Config {
    pub(crate) fn load() -> Self {
        let cfg: Self = match confy::load(env!("CARGO_PKG_NAME"), "config") {
            Ok(data) => data,
            Err(e) => panic!("config.toml not found: {e}"),
        };

        if cfg.instance.is_empty() {
            panic!("bot instance not set!")
        }

        if cfg.username.is_empty() {
            panic!("bot username not set!")
        }

        if cfg.password.is_empty() {
            panic!("bot password not provided!")
        }

        cfg.series.iter().for_each(|series| {
            if series.prepub_community.post_body == Description {
                panic!("'Description' type Post Body only supported for Volumes!")
            }
        });
        cfg
    }

    pub(crate) fn get_path() -> PathBuf {
        confy::get_configuration_file_path(env!("CARGO_PKG_NAME"), "config").expect("Application will not without confy")
    }

    pub(crate) fn get_username(&self) -> Sensitive<String> {
        Sensitive::new(self.username.clone())
    }

    pub(crate) fn get_password(&self) -> Sensitive<String> {
        Sensitive::new(self.password.clone())
    }
}

impl Default for Config {
    fn default() -> Self {
        Config {
            instance: "".to_owned(),
            username: "".to_owned(),
            password: "".to_owned(),
            status_post_url: None,
            config_reload_seconds: 21600,
            protected_communities: vec![],
            series: vec![],
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub(crate) struct SeriesConfig {
    pub(crate) slug: String,
    pub(crate) parted: bool,
    pub(crate) prepub_community: PostConfig,
    pub(crate) volume_community: PostConfig,
    pub(crate) fetcher: Fetcher
}

impl SeriesConfig {
    pub(crate) async fn update(&self, history: &mut SeriesHistory, lemmy: &Lemmy, config: &Arc<RwLock<Config>>) {
        let info_msg = format!("Checking {} for Updates", self.slug);
        info!(info_msg);

        let mut fetcher: Fetcher = match &self.fetcher {
            Fetcher::Jnc(_) => {
                Fetcher::Jnc(JNovelFetcher::new())
            },
            /*default => {
                let err_msg = format!("Fetcher {default} not implemented");
                error!(err_msg);
                return;
            }*/
        };

        match fetcher {
            Fetcher::Jnc(ref mut jnc) => {
                jnc.set_series(self.slug.clone());
                jnc.set_part_option(self.parted);
            }
        }

        let post_list = match fetcher.check_feed().await {
            Ok(data) => data,
            Err(_) => {
                let err_msg = format!("While checking feed for {}", self.slug);
                error!(err_msg);
                return;
            }
        };

        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.iter() {
            if history.check_for_post(
                self.slug.as_str(),
                post_info.get_part_info().unwrap_or(PartInfo::NoParts).as_string().as_str(),
                post_info.get_info().title.as_str()
            ) {
                continue
            }

            let post_data = post_info.get_post_data(self, lemmy);

            let info = format!(
                "Posting '{}' to {}",
                post_info.get_info().title.as_str(),
                post_info.get_post_config(self).name.as_str()
            );
            info!(info);

            let post_id = match lemmy.post(post_data).await {
                Ok(data) => data,
                Err(_) => {
                    error!("Error posting chapter");
                    return;
                }
            };

            let read_config = config.read().expect("Read Lock Failed").clone();

            if post_info.get_post_config(self).pin_settings.pin_new_post_community
                && !read_config
                .protected_communities
                .contains(&post_info.get_post_config(self).name)
            {
                let info = format!(
                    "Pinning '{}' to {}",
                    post_info.get_info().title,
                    post_info.get_post_config(self).name.as_str()
                );
                info!(info);
                let pinned_posts = match lemmy.get_community_pinned(lemmy.get_community_id(&post_info.get_post_config(self).name)).await {
                    Ok(data) => data,
                    Err(_) => {
                        error!("Pinning of Post to community failed");
                        continue;
                    }
                };
                if !pinned_posts.is_empty() {
                    let community_pinned_post = &pinned_posts[0];
                    match lemmy
                        .unpin(community_pinned_post.post.id, PostFeatureType::Community)
                        .await {
                        Ok(_) => {}
                        Err(_) => {
                            error!("Error un-pinning post");
                            return;
                        }
                    }
                }
                match lemmy.pin(post_id, PostFeatureType::Community).await {
                    Ok(_) => {}
                    Err(_) => {
                        error!("Error pinning post");
                        return;
                    }
                }
            } else if read_config
                .protected_communities
                .contains(&post_info.get_post_config(self).name)
            {
                let message = format!(
                    "Community '{}' for Series '{}' is protected. Is this intended?",
                    &post_info.get_post_config(self).name, self.slug
                );
                warn!(message);
            }

            if post_info.get_post_config(self).pin_settings.pin_new_post_local {
                let info = format!("Pinning '{}' to Instance", post_info.get_info().title);
                info!(info);
                let pinned_posts = match lemmy.get_local_pinned().await {
                    Ok(data) => {data}
                    Err(_) => {
                        error!("Error fetching pinned posts");
                        return;
                    }
                };

                if !pinned_posts.is_empty() {
                    for pinned_post in pinned_posts {
                        if read_config
                            .protected_communities
                            .contains(&pinned_post.community.name)
                        {
                            continue;
                        } else {
                            let community_pinned_post = &pinned_post;
                            match lemmy
                                .unpin(community_pinned_post.post.id, PostFeatureType::Local)
                                .await {
                                Ok(_) => {}
                                Err(_) => {
                                    error!("Error pinning post");
                                    return;
                                }
                            }
                            break;
                        }
                    }
                }
                match lemmy.pin(post_id, PostFeatureType::Local).await {
                    Ok(_) => {}
                    Err(_) => {
                        error!("Error pinning post");
                        return;
                    }
                };
            }

            let mut series_history = history.get_series(self.slug.as_str());
            let mut part_history = series_history.get_part(post_info.get_part_info().unwrap_or(PartInfo::NoParts).as_string().as_str());

            match post_info.post_type {
                Some(post_type) => {
                    match post_type {
                        PostType::Chapter => part_history.chapter = post_info.get_info().title,
                        PostType::Volume => part_history.volume = post_info.get_info().title,
                    }
                }
                None => part_history.chapter = post_info.get_info().title,
            }

            series_history.set_part(post_info.get_part_info().unwrap_or(PartInfo::NoParts).as_string().as_str(), part_history);
            history
                .set_series(self.slug.as_str(), series_history);
            history.save_history();
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub(crate) struct PostConfig {
    pub(crate) name: String,
    pub(crate) pin_settings: PinConfig,
    pub(crate) post_body: PostBody,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub(crate) struct PinConfig {
    pub(crate) pin_new_post_local: bool,
    pub(crate) pin_new_post_community: bool,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "body_type", content = "body_content")]
pub(crate) enum PostBody {
    None,
    Description,
    Custom(String),
}