use chrono::Utc;
use config::{Config, PrevPost, Secrets, LemmyCommunities};
use lemmy_api_common::{
    person::{Login, LoginResponse},
    post::{CreatePost, GetPosts, GetPostsResponse},
    sensitive::Sensitive, community::{ListCommunities, ListCommunitiesResponse},
};
use lemmy_db_schema::{
    newtypes::{CommunityId, LanguageId},
    ListingType, SortType,
};
use once_cell::sync::Lazy;
use reqwest::{blocking::Client, StatusCode};
use std::{thread::sleep, time};
use url::Url;

use crate::config::FeedData;
mod config;

pub static CLIENT: Lazy<Client> = Lazy::new(|| {
    let client = Client::builder()
        .timeout(time::Duration::from_secs(30))
        .connect_timeout(time::Duration::from_secs(30))
        .build()
        .expect("build client");
    client
});

struct Bot {
    secrets: Secrets,
    config: Config,
    post_history: Vec<PrevPost>,
    community_ids: CommunitiesVector,
    auth: Sensitive<String>,
}

impl Bot {
    pub(crate) fn new() -> Bot {
        Bot {
            secrets: Secrets::load(),
            config: Config::load(),
            post_history: PrevPost::load(),
            community_ids: CommunitiesVector::new(),
            auth: Sensitive::new("".to_string()),
        }
    }

    pub(crate) fn login(&mut self) {
        let login_params = Login {
            username_or_email: self.secrets.lemmy.get_username(),
            password: self.secrets.lemmy.get_password(),
        };

        let res = CLIENT
            .post(self.config.instance.clone() + "/api/v3/user/login")
            .json(&login_params)
            .send()
            .unwrap();

        if res.status() == StatusCode::OK {
            let data: &LoginResponse = &res.json().unwrap();

            let jwt = data.jwt.clone().expect("JWT Token could not be acquired");
            self.auth = jwt;
        } else {
            println!("Error Code: {:?}", res.status());
            panic!("JWT Token could not be acquired");
        }
    }

    pub(crate) fn post(&mut self, post_data: CreatePost) {
        let res = CLIENT
            .post(self.config.instance.clone() + "/api/v3/post")
            .json(&post_data)
            .send()
            .unwrap();
    }
}

fn list_posts(auth: &Sensitive<String>, base: String) -> GetPostsResponse {
    let params = GetPosts {
        type_: Some(ListingType::Local),
        sort: Some(SortType::New),
        auth: Some(auth.clone()),
        ..Default::default()
    };

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

    return serde_json::from_str(&res).unwrap();
}

struct CommunitiesVector {
    ids: Vec<(CommunityId, String)>,
}

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

    fn load(&mut self, auth: &Sensitive<String>, base: &String) {
        let params = ListCommunities {
            auth: Some(auth.clone()),
            ..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;
    }

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

fn main() {
    // Get all needed auth tokens at the start
    let mut old = Utc::now().time();
    let mut this = Bot::new();
    println!("{}", this.secrets.lemmy.username);
    this.login();
    this.community_ids.load(&this.auth, &this.config.instance);

    // Create empty eTag list
    println!("TODO: Etag list");

    // Enter a loop (not for debugging)
    loop {
        let start = Utc::now();
        print!("\x1B[2J\x1B[1;1H");
        println!(
            "Started loop at {} {}",
            start.format("%H:%M:%S"),
            start.timezone()
        );

        if start.time() - old > chrono::Duration::seconds(6) {
            old = start.time();
            this.config = Config::load();
            this.community_ids.load(&this.auth, &this.config.instance);
        }

        // Start the polling process
        // Get all feed URLs (use cache)
        let mut post_queue: Vec<CreatePost> = vec![];
        this.config.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;
            this.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: this.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: this.auth.clone(),
                };
                post_queue.push(new_post);
                match prev_post_idx {
                    Some(idx) => {
                        this.post_history[idx].title = data.title;
                        this.post_history[idx].last_post_url = item.url.clone();
                    }
                    None => this.post_history.push(PrevPost {
                        title: data.title,
                        last_post_url: item.url.clone(),
                    }),
                }
            }
            sleep(time::Duration::from_millis(100)); // Should prevent dos-ing J-Novel servers
        });

        PrevPost::save(&this.post_history);
        post_queue.iter().for_each(|post| {
            println!("Posting: {}", post.name);
            this.post(post.clone());
        });

        while Utc::now().time() - start.time() < chrono::Duration::seconds(60) {
            sleep(time::Duration::from_secs(10));
        }
    }
}