use std::{fs::{self, OpenOptions}, path::Path, io::Write, thread::sleep, time};

use lemmy_api_common::{sensitive::Sensitive, post::CreatePost, community::{self, ListCommunities, ListCommunitiesResponse}};
use lemmy_db_schema::{newtypes::{LanguageId, CommunityId}, ListingType};
use serde_derive::{Deserialize, Serialize};
use url::Url;

use crate::{CLIENT};

macro_rules! pub_struct {
    ($name:ident {$($field:ident: $t:ty,)*}) => {
        #[derive(Serialize, Deserialize, Clone, PartialEq)]
        pub(crate) struct $name {
            $(pub(crate) $field: $t), *
        }
    }
}

// Secrets structs
pub_struct!(Secrets {
    lemmy: LemmyLogin,
    reddit: RedditLogin,
});

impl Secrets {
    pub(crate) fn init() -> Secrets {
        let file_contents = match fs::read_to_string("secrets.json") {
            Ok(data) => data,
            Err(e) => panic!("ERROR: secrets.json could not be read:\n\n{:#?}", e),
        };
        let config_parse: Secrets = match serde_json::from_str(&file_contents) {
            Ok(data) => data,
            Err(e) => panic!("ERROR: secrets.json could not be parsed:\n\n{:#?}", e),
        };

        return config_parse;
    }
}

#[derive(Serialize, Deserialize, Clone, PartialEq)]
pub(crate) struct LemmyLogin {
    pub(crate) username: String,
    password: String
}

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

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

#[derive(Serialize, Deserialize, Clone, PartialEq)]
pub(crate) struct RedditLogin {
    pub(crate) app_id: String,
    app_secret: String,
    refresh_token: String,
    redirect_uri: String,
}

// Config structs
pub_struct!(Config {
    instance: String,
    reddit_config: RedditConfig,
    feeds: Vec<FeedSetting>,
});

impl Config {
    pub(crate) fn init() -> Config {
        let file_contents = match fs::read_to_string("config.json") {
            Ok(data) => data,
            Err(e) => panic!("ERROR: config.json could not be read:\n\n{:#?}", e),
        };
        let config_parse: Config = match serde_json::from_str(&file_contents) {
            Ok(data) => data,
            Err(e) => panic!("ERROR: config.json could not be parsed:\n\n{:#?}", e),
        };

        return config_parse;
    }

    pub(crate) fn load(&mut self) {
        let file_contents = match fs::read_to_string("config.json") {
            Ok(data) => data,
            Err(e) => panic!("ERROR: config.json could not be read:\n\n{:#?}", e),
        };
        let config_parse: Config = match serde_json::from_str(&file_contents) {
            Ok(data) => data,
            Err(e) => panic!("ERROR: config.json could not be parsed:\n\n{:#?}", e),
        };

        self.feeds = config_parse.feeds;
        self.instance = config_parse.instance;
        self.reddit_config = config_parse.reddit_config;
    }

    pub(crate) fn check_feeds(&mut self, post_history: &mut Vec<PrevPost>
    , community_ids: &CommunitiesVector, auth: &Sensitive<String>) -> Vec<CreatePost> {
        let mut post_queue: Vec<CreatePost> = vec![];

        self.feeds.iter().for_each(|feed| {
            let res = CLIENT
                .get(feed.feed_url.clone())
                .send()
                .unwrap()
                .text()
                .unwrap();
            let data: FeedData = serde_json::from_str(&res).unwrap();

            let mut prev_post_idx: Option<usize> = None;
            let mut do_post = true;
            post_history
                .iter()
                .enumerate()
                .for_each(|(idx, post)| {
                    if &post.last_post_url == &data.items[0].url {
                        do_post = false;
                    } else if &post.title == &data.title {
                        prev_post_idx = Some(idx);
                    }
                });

            if do_post {
                let item = &data.items[0];
                let new_post = CreatePost {
                    name: item.title.clone(),
                    community_id: community_ids.find(&feed.communities.chapter),
                    url: Some(Url::parse(&item.url).unwrap()),
                    body: Some(
                        "[Reddit](https://reddit.com)\n\n[Discord](https://discord.com)".into(),
                    ),
                    honeypot: None,
                    nsfw: Some(false),
                    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
                    auth: auth.clone(),
                };
                post_queue.push(new_post);
                match prev_post_idx {
                    Some(idx) => {
                        post_history[idx].title = data.title;
                        post_history[idx].last_post_url = item.url.clone();
                    }
                    None => post_history.push(PrevPost {
                        id: feed.id,
                        title: data.title,
                        last_post_url: item.url.clone(),
                    }),
                }
            }
            sleep(time::Duration::from_millis(100)); // Should prevent dos-ing J-Novel servers
        });

        PrevPost::save(&post_history);
        return post_queue;
    }
}

pub_struct!(RedditConfig {
    user_agent: String,
    subreddit: String,
});

pub_struct!(FeedSetting {
    id: usize,
    feed_url: String,
    communities: FeedCommunities,
    reddit: FeedRedditSettings,
});

pub_struct!(FeedCommunities {
    chapter: LemmyCommunities,
    volume: LemmyCommunities,
});

#[derive(Serialize, Deserialize, Clone, PartialEq, strum_macros::Display)]
#[allow(non_camel_case_types)]
pub(crate) enum LemmyCommunities {
    aobwebnovel,
    aobprepub,
    aoblightnovel,
    aobmanga,
    aobanime
}

pub_struct!(FeedRedditSettings {
    enabled: bool,
    flair: String,
});

// Posts structs
pub_struct!(PrevPost {
    id: usize,
    title: String,
    last_post_url: String,
});

impl PrevPost {
    pub(crate) fn load() -> Vec<PrevPost> {
        let history;
        
        if Path::new("posts.json").exists() {
            let file_contents = match fs::read_to_string("posts.json") {
                Ok(data) => data,
                Err(e) => panic!("ERROR: secrets.json could not be read:\n\n{:#?}", e),
            };

            if file_contents.len() > 0 {
                let history_parse: Vec<PrevPost> = match serde_json::from_str(&file_contents) {
                    Ok(data) => data,
                    Err(e) => panic!("ERROR: secrets.json could not be parsed:\n\n{:#?}", e),
                };
                history = history_parse;
            }
            else {
                history = [].to_vec()
            }
        }
        else {
            let _ = fs::File::create("posts.json");
            history = [].to_vec()
        }

        return history;
    }

    pub(crate) fn save(data: &Vec<PrevPost>) {
        let mut file = OpenOptions::new().read(true).write(true).create(true).open("posts.json").unwrap();

        let json_data = serde_json::to_string_pretty(&data).unwrap();

        write!(&mut file, "{}", json_data).unwrap();
    }
}

// RSS Feed Structs
pub_struct!(FeedData {
    version: String,
    title: String,
    home_page_url: String,
    description: String,
    author: FeedAuthor,
    items: Vec<FeedEntry>,
});

pub_struct!(FeedAuthor {
    name: String,
});

pub_struct!(FeedEntry {
    id: String,
    url: String,
    title: String,
    summary: String,
    image: Option<String>,
    date_published: String,
});
  
// Bot Helper Structs
pub_struct!(CommunitiesVector {
    ids: Vec<(CommunityId, String)>,
});

impl CommunitiesVector {
    pub(crate) fn new() -> CommunitiesVector {
        CommunitiesVector{ids: vec![]}
    }

    pub(crate) fn load(&mut self, auth: &Sensitive<String>, base: &String) {
        let params = ListCommunities {
            auth: Some(auth.clone()),
            type_: Some(ListingType::Local),
            ..Default::default()
        };

        let res = CLIENT
            .get(base.clone() + "/api/v3/community/list")
            .query(&params)
            .send()
            .unwrap()
            .text()
            .unwrap();

        let site_data: ListCommunitiesResponse = serde_json::from_str(&res).unwrap();

        let mut ids = [].to_vec();

        site_data.communities.iter().for_each(|entry| {
            let new_id = (entry.community.id, entry.community.name.clone());
            ids.push(new_id);
        });

        self.ids = ids;
    }

    pub(crate) fn find(&self, name: &LemmyCommunities) -> CommunityId {
        let mut ret_id = CommunityId(0);

        self.ids.iter().for_each(|id| {
            let id_name = &id.1;
            if &name.to_string() == id_name {
                ret_id = id.0;
            }
        });
        return  ret_id;
    }
}