use std::collections::HashMap;
use lemmy_api_common::community::{ListCommunities, ListCommunitiesResponse};
use lemmy_api_common::lemmy_db_views::structs::PostView;
use lemmy_api_common::person::{Login, LoginResponse};
use lemmy_api_common::post::{CreatePost, FeaturePost, GetPosts, GetPostsResponse};
use lemmy_api_common::sensitive::Sensitive;
use lemmy_db_schema::newtypes::{CommunityId, PostId};
use lemmy_db_schema::{ListingType, PostFeatureType};
use reqwest::StatusCode;
use crate::config::Config;
use crate::HTTP_CLIENT;

pub(crate) struct Credentials {
    username: String,
    password: String
}

impl Credentials {
    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())
    }

    pub(crate) fn set_credentials(config: &Config) -> Self {
        Self {
            username: config.username.clone(),
            password: config.password.clone(),
        }
    }
}

pub(crate) struct Lemmy {
    jwt_token: Sensitive<String>,
    instance: String,
}

pub(crate) async fn login(credentials: &Credentials, instance: &str) -> Result<Lemmy, String> {
    let login_params = Login {
        username_or_email: credentials.get_username(),
        password: credentials.get_password(),
        totp_2fa_token: None,
    };

    let response = match HTTP_CLIENT
        .post(instance.to_owned() + "/api/v3/user/login")
        .json(&login_params)
        .send()
        .await {
        Ok(data) => data,
        Err(e) => return Err(format!("{}", e))
    };

    match response.status() {
        StatusCode::OK => {
            let data: LoginResponse = response.json().await.expect("Successful Login Request should return JSON");
            match data.jwt {
                Some(token) => Ok(Lemmy {
                    jwt_token: token.clone(),
                    instance: instance.to_owned(),
                }),
                None => panic!("Login did not return JWT token. Are the credentials valid?")
            }
        },
        status => panic!("Unexpected HTTP Status '{}' during Login", status)
    }
}

impl Lemmy {
    pub(crate) async fn post(&self, post: CreatePost) -> Result<PostId, String> {
        let response = match HTTP_CLIENT
            .post(format!("{}/api/v3/post", &self.instance))
            .bearer_auth(&self.jwt_token.to_string())
            .json(&post)
            .send()
            .await {
            Ok(data) => {
                match data.text().await {
                    Ok(data) => data,
                    Err(e) => return Err(format!("{}", e))
                }
            },
            Err(e) => return Err(format!("{}", e))
        };

        let json_data = match serde_json::from_str::<HashMap<&str, PostView>>(&response) {
            Ok(mut data) => data.remove("post_view").expect("Element should be present"),
            Err(e) => return Err(format!("{}", e))
        };

        Ok(json_data.post.id)
    }

    async fn feature(&self, params: FeaturePost) -> Result<PostView, String> {
        let response = match HTTP_CLIENT
            .post(format!("{}/api/v3/post/feature", &self.instance))
            .bearer_auth(&self.jwt_token.to_string())
            .json(&params)
            .send()
            .await {
            Ok(data) => {
                match data.text().await {
                    Ok(data) => data,
                    Err(e) => return Err(format!("{}", e))
                }
            },
            Err(e) => return Err(format!("{}", e))
        };

        let json_data = match serde_json::from_str::<HashMap<&str, PostView>>(&response) {
            Ok(mut data) => data.remove("post_view").expect("Element should be present"),
            Err(e) => return Err(format!("{}", e))
        };

        Ok(json_data)
    }

    pub(crate) async fn unpin(&self, post_id: PostId, location: PostFeatureType) -> Result<PostView, String> {
        let pin_params = FeaturePost {
            post_id,
            featured: false,
            feature_type: location,
        };
        self.feature(pin_params).await
    }

    pub(crate) async fn pin(&self, post_id: PostId, location: PostFeatureType) -> Result<PostView, String> {
        let pin_params = FeaturePost {
            post_id,
            featured: true,
            feature_type: location,
        };
        self.feature(pin_params).await
    }

    pub(crate) async fn get_community_pinned(&self, community: CommunityId) -> Result<Vec<PostView>, String> {
        let list_params = GetPosts {
            community_id: Some(community),
            type_: Some(ListingType::Local),
            ..Default::default()
        };

        let response = match HTTP_CLIENT
            .get(format!("{}/api/v3/post/list", &self.instance))
            .bearer_auth(&self.jwt_token.to_string())
            .query(&list_params)
            .send()
            .await {
            Ok(data) => {
                match data.text().await {
                    Ok(data) => data,
                    Err(e) => return Err(format!("{}", e))
                }
            },
            Err(e) => return Err(format!("{}", e))
        };

        let json_data: GetPostsResponse = match serde_json::from_str(&response) {
            Ok(data) => data,
            Err(e) => return Err(format!("{}", e))
        };

        Ok(json_data.posts.iter().filter(|post| {
                post.post.featured_community
            })
            .cloned()
            .collect()
        )
    }

    pub(crate) async fn get_local_pinned(&self) -> Result<Vec<PostView>, String> {
        let list_params = GetPosts {
            type_: Some(ListingType::Local),
            ..Default::default()
        };

        let response = match HTTP_CLIENT
            .get(format!("{}/api/v3/post/list", &self.instance))
            .bearer_auth(&self.jwt_token.to_string())
            .query(&list_params)
            .send()
            .await {
            Ok(data) => {
                match data.text().await {
                    Ok(data) => data,
                    Err(e) => return Err(format!("{}", e))
                }
            },
            Err(e) => return Err(format!("{}", e))
        };

        let json_data: GetPostsResponse = match serde_json::from_str(&response) {
            Ok(data) => data,
            Err(e) => return Err(format!("{}", e))
        };

        Ok(json_data.posts.iter().filter(|post| {
            post.post.featured_local
        })
            .cloned()
            .collect()
        )
    }

    pub(crate) async fn get_communities(&self) -> Result<HashMap<String, CommunityId>, String> {
        let list_params = ListCommunities {
            type_: Some(ListingType::Local),
            ..Default::default()
        };

        let response = match HTTP_CLIENT
            .get(format!("{}/api/v3/community/list", &self.instance))
            .bearer_auth(&self.jwt_token.to_string())
            .query(&list_params)
            .send()
            .await {
            Ok(data) => {
                match data.text().await {
                    Ok(data) => data,
                    Err(e) => return Err(format!("{}", e))
                }
            },
            Err(e) => return Err(format!("{}", e))
        };

        let json_data: ListCommunitiesResponse = match serde_json::from_str(&response) {
            Ok(data) => data,
            Err(e) => return Err(format!("{}", e))
        };

        let mut communities: HashMap<String, CommunityId> = HashMap::new();
        for community_view in json_data.communities {
            let community = community_view.community;
            communities.insert(community.name, community.id);
        }

        Ok(communities)
    }
}