mod serial;

#[cfg(test)]
mod tests;

use chrono::Utc;
use derive_more::From;
use reqwest::StatusCode;
use rocket::http::{Cookie, CookieJar};
use serial::*;

use serde::Deserialize;

use crate::cookies::{token::*, user::*};

#[derive(Default, Deserialize)]
pub struct User {
    #[serde(deserialize_with = "deserialize_id")]
    pub id: u64,
    #[serde(rename = "username")]
    pub name: String,
    #[serde(deserialize_with = "deserialize_discriminator")]
    pub discriminator: u16,
    pub avatar: String,
}

impl User {
    pub fn username(&self) -> String {
        if self.discriminator == 0 {
            return self.name.clone();
        }
        format!("{}#{:0>4}", self.name, self.discriminator)
    }

    pub async fn init(token: &str, cookies: &CookieJar<'_>) -> Result<Self, GetUserError> {
        let (status, text) = {
            let response = reqwest::Client::new()
                .get("https://discord.com/api/users/@me")
                .header("Authorization", format!("Bearer {token}"))
                .send()
                .await?;
            (response.status(), response.text().await)
        };
        if !status.is_success() {
            return Err(GetUserError::DiscordError {
                status,
                message: text.ok(),
            });
        }
        let user: Self = serde_json::from_str(&text?)?;
        cookies.add_private(Cookie::new(TOKEN_COOKIE, token.to_owned()));
        cookies.add_private(Cookie::new(USER_ID_COOKIE, user.id.to_string()));
        cookies.add_private(Cookie::new(USER_NAME_COOKIE, user.name.clone()));
        cookies.add_private(Cookie::new(
            USER_DISCRIMINATOR_COOKIE,
            user.discriminator.to_string(),
        ));
        cookies.add_private(Cookie::new(USER_AVATAR_COOKIE, user.avatar.clone()));
        Ok(user)
    }

    pub fn purge(cookies: &CookieJar<'_>) {
        cookies.remove_private(Cookie::named(TOKEN_COOKIE));
        cookies.remove_private(Cookie::named(USER_ID_COOKIE));
        cookies.remove_private(Cookie::named(USER_NAME_COOKIE));
        cookies.remove_private(Cookie::named(USER_DISCRIMINATOR_COOKIE));
        cookies.remove_private(Cookie::named(USER_AVATAR_COOKIE));
        cookies.remove(Cookie::named(TOKEN_EXPIRE_COOKIE));
    }

    fn from_cookies(cookies: &CookieJar<'_>) -> Option<Self> {
        Some(Self {
            id: parse_cookie_value(cookies, USER_ID_COOKIE)?,
            name: parse_cookie_value(cookies, USER_NAME_COOKIE)?,
            discriminator: parse_cookie_value(cookies, USER_DISCRIMINATOR_COOKIE)?,
            avatar: parse_cookie_value(cookies, USER_AVATAR_COOKIE)?,
        })
    }

    pub async fn get(cookies: &CookieJar<'_>) -> Result<Option<Self>, GetUserError> {
        let user = match Self::from_cookies(cookies) {
            Some(user) => user,
            None => return Ok(None),
        };
        if cookies
            .get(TOKEN_EXPIRE_COOKIE)
            .map(|expire| expire.value().parse::<i64>())
            .and_then(Result::ok)
            .map_or(true, |timestamp| Utc::now().timestamp() >= timestamp)
        {
            cookies.remove_private(Cookie::named(TOKEN_COOKIE));
            cookies.remove_private(Cookie::named(USER_ID_COOKIE));
            cookies.remove_private(Cookie::named(USER_NAME_COOKIE));
            cookies.remove_private(Cookie::named(USER_DISCRIMINATOR_COOKIE));
            cookies.remove_private(Cookie::named(USER_AVATAR_COOKIE));
            cookies.remove(Cookie::named(TOKEN_EXPIRE_COOKIE));
            return Ok(None);
        }
        Ok(Some(user))
    }
}

#[derive(From, Debug)]
pub enum GetUserError {
    ReqwestError(reqwest::Error),
    DeserializeError(serde_json::Error),
    #[allow(unused)]
    DiscordError {
        status: StatusCode,
        message: Option<String>,
    },
}

fn parse_cookie_value<T: std::str::FromStr>(cookies: &CookieJar<'_>, name: &str) -> Option<T> {
    cookies.get_private(name)?.value().parse().ok()
}