use crate::{HTTP_CLIENT};
use chrono::{DateTime, Duration, Utc};
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
use std::ops::Sub;
use async_trait::async_trait;
use crate::fetchers::{FetcherTrait};
use crate::lemmy::{PartInfo, PostInfo, PostInfoInner, PostType};
use systemd_journal_logger::connected_to_journal;
use crate::lemmy::PartInfo::{NoParts, Part};

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

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

static PAST_DAYS_ELIGIBLE: u8 = 4;

macro_rules! api_url {
    () => {
        "https://labs.j-novel.club/app/v1".to_owned()
    };
}

macro_rules! jnc_base_url {
    () => {
        "https://j-novel.club".to_owned()
    };
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct VolumesWrapper {
    volumes: Vec<VolumeDetail>,
    pagination: PaginationInfo,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct ChapterWrapper {
    parts: Vec<ChapterDetail>,
    pagination: PaginationInfo,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct PaginationInfo {
    limit: usize,
    skip: usize,
    #[serde(alias = "lastPage")]
    last_page: bool,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub(crate) struct Cover {
    #[serde(alias = "coverUrl")]
    pub(crate) cover: String,
    #[serde(alias = "thumbnailUrl")]
    pub(crate) thumbnail: String,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub(crate) struct VolumeDetail {
    pub(crate) title: String,
    pub(crate) slug: String,
    number: u8,
    publishing: String,
    #[serde(alias = "shortDescription")]
    short_description: String,
    cover: Cover,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub(crate) struct ChapterDetail {
    pub(crate) title: String,
    pub(crate) slug: String,
    launch: String,
    pub(crate) cover: Option<Cover>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub(crate) struct JNovelFetcher {
    series_slug: String,
    series_has_parts: bool
}

impl Default for JNovelFetcher {
    fn default() -> Self {
        Self {
            series_slug: "".to_owned(),
            series_has_parts: false,
        }
    }
}

impl JNovelFetcher {
    pub(crate) fn set_series(&mut self, series: String) {
        self.series_slug = series;
    }

    pub(crate) fn set_part_option(&mut self, has_parts: bool) {
        self.series_has_parts = has_parts;
    }
}

#[async_trait]
impl FetcherTrait for JNovelFetcher {
    fn new() -> Self {
        JNovelFetcher {
            series_slug: "".to_owned(),
            series_has_parts: false
        }
    }

    async fn check_feed(&self) -> Result<Vec<PostInfo>, ()> {
        let response = match HTTP_CLIENT
            .get(api_url!() + "/series/" + self.series_slug.as_str() + "/volumes?format=json")
            .send()
            .await
        {
            Ok(data) => match data.text().await {
                Ok(data) => data,
                Err(e) => {
                    let err_msg = format!("While checking feed: {e}");
                    error!(err_msg);
                    return Err(());
                }
            },
            Err(e) => {
                let err_msg = format!("{e}");
                error!(err_msg);
                return Err(());
            }
        };

        let mut volume_brief_data: VolumesWrapper = match serde_json::from_str(&response) {
            Ok(data) => data,
            Err(e) => {
                let err_msg = format!("{e}");
                error!(err_msg);
                return Err(());
            }
        };
        volume_brief_data.volumes.reverse(); // Makes breaking out of the volume loop easier

        // If no parts just use 0 as Part indicator as no Series with Parts has a Part 0
        let mut volume_map: HashMap<u8, PostInfo> = HashMap::new();
        let mut prepub_map: HashMap<u8, PostInfo> = HashMap::new();

        for volume in volume_brief_data.volumes.iter() {
            let publishing_date = DateTime::parse_from_rfc3339(&volume.publishing).unwrap();
            if publishing_date < Utc::now().sub(Duration::days(PAST_DAYS_ELIGIBLE as i64)) {
                match self.series_has_parts {
                    true => continue,
                    false => break,
                }
            }
            let new_part_info: PartInfo;

            if self.series_has_parts {
                let mut part_number: Option<u8> = None;
                let splits: Vec<&str> = volume.slug.split('-').collect();
                for (index, split) in splits.clone().into_iter().enumerate() {
                    if split == "part" {
                        part_number = Some(
                            splits[index + 1]
                                .parse::<u8>()
                                .expect("Split Element after 'Part' should always be a number"),
                        );
                        break;
                    }
                }

                match part_number {
                    Some(number) => new_part_info = Part(number),
                    None => {
                        info!("No Part found, assuming 1");
                        new_part_info = Part(1);
                    }
                }
            } else {
                new_part_info = NoParts;
            }

            let post_url = format!(
                "{}/series/{}#volume-{}",
                jnc_base_url!(),
                self.series_slug.as_str(),
                volume.number
            );
            let post_details = PostInfoInner {
                title: volume.title.clone(),
                url: post_url.clone(),
                thumbnail: Some(volume.cover.thumbnail.clone())
            };

            let new_post_info = PostInfo {
                post_type: Some(PostType::Volume),
                part: Some(new_part_info),
                description: Some(volume.short_description.clone()),
                lemmy_info: post_details,
            };

            let part_id = new_part_info.as_u8();

            if publishing_date <= Utc::now() {
                volume_map
                    .entry(part_id)
                    .and_modify(|val| {
                        if *val < new_post_info {
                            *val = new_post_info.clone()
                        }
                    })
                    .or_insert(new_post_info);
            }

            if let Some(prepub_info) = get_latest_prepub(&volume.slug).await {
                let prepub_post_info = PostInfo {
                    post_type: Some(PostType::Chapter),
                    part: Some(new_part_info),
                    lemmy_info: prepub_info,
                    description: None,
                };

                prepub_map
                    .entry(part_id)
                    .and_modify(|val| {
                        if *val < prepub_post_info {
                            *val = prepub_post_info.clone()
                        }
                    })
                    .or_insert(prepub_post_info);
            }
        }

        let mut result_vec: Vec<PostInfo> = volume_map.values().cloned().collect();
        let mut prepub_vec: Vec<PostInfo> = prepub_map.values().cloned().collect();
        result_vec.append(&mut prepub_vec);

        Ok(result_vec)
    }
}


async fn get_latest_prepub(volume_slug: &str) -> Option<PostInfoInner> {
    let response = match HTTP_CLIENT
        .get(api_url!() + "/volumes/" + volume_slug + "/parts?format=json")
        .send()
        .await
    {
        Ok(data) => match data.text().await {
            Ok(data) => data,
            Err(e) => {
                let err_msg = format!("While getting latest PrePub: {e}");
                error!(err_msg);
                return None;
            }
        },
        Err(e) => {
            let err_msg = format!("{e}");
            error!(err_msg);
            return None;
        }
    };

    let mut volume_prepub_parts_data: ChapterWrapper = match serde_json::from_str(&response) {
        Ok(data) => data,
        Err(e) => {
            let err_msg = format!("{e}");
            error!(err_msg);
            return None;
        }
    };
    volume_prepub_parts_data.parts.reverse(); // Makes breaking out of the parts loop easier

    let mut post_details: Option<PostInfoInner> = None;

    for prepub_part in volume_prepub_parts_data.parts.iter() {
        let publishing_date = DateTime::parse_from_rfc3339(&prepub_part.launch).unwrap();
        if publishing_date > Utc::now() {
            break;
        } else if publishing_date < Utc::now().sub(Duration::days(PAST_DAYS_ELIGIBLE as i64)) {
            continue;
        }

        let thumbnail = prepub_part.cover.as_ref().map(|cover| cover.thumbnail.clone());

        let post_url = format!("{}/read/{}", jnc_base_url!(), prepub_part.slug);
        post_details = Some(PostInfoInner {
            title: prepub_part.title.clone(),
            url: post_url.clone(),
            thumbnail
        });
    }

    post_details
}