use argon2::{Argon2, PasswordHash, PasswordVerifier};
use redis::AsyncCommands;
use rocket::{http::Status, serde::json::Json, time::OffsetDateTime};
use rocket_db_pools::Connection;

use crate::{
    auth::{Session, two_factor::totp_gen},
    db::{Postgres, Redis},
};

pub struct User {
    pub id: i32,
    pub email: Option<String>,
    pub username: String,
    pub display_name: Option<String>,
    pub pass_hash: String,
    pub twofa_enabled: bool,
    pub totp_secret: Option<String>,
    pub created_at: Option<OffsetDateTime>,
    pub updated_at: Option<OffsetDateTime>,
}

impl User {
    pub async fn get_by_id(id: usize, db: &mut Connection<Postgres>) -> Option<Self> {
        sqlx::query_as!(
            Self,
            "SELECT id, email, username, display_name, pass_hash, twofa_enabled, totp_secret, created_at, updated_at FROM users WHERE id = $1",
            id as i32
        )
        .fetch_optional(&mut ***db)
        .await
        .unwrap_or(None)
    }

    pub async fn delete(&mut self, db: &mut Connection<Postgres>) -> Result<(), sqlx::Error> {
        sqlx::query!("DELETE FROM users WHERE id = $1", self.id)
            .execute(&mut ***db)
            .await?;
        Ok(())
    }

    pub fn verify_2fa(&self, code: &str) -> Result<(), Status> {
        if totp_gen(
            self.id as usize,
            self.totp_secret
                .clone()
                .expect("user with 2fa enabled has no totp secret")
                .as_bytes(),
        )
        .map_err(|_| Status::InternalServerError)?
        .check_current(code)
        .map_err(|_| Status::InternalServerError)?
        {
            Ok(())
        } else {
            Err(Status::Unauthorized)
        }
    }

    pub fn verify_password(&self, password: &str) -> Result<(), Status> {
        let parsed_hash = PasswordHash::new(&self.pass_hash)
            .inspect_err(|e| {
                tracing::error!("Failed to parse hash for password! uid:{} {e}", self.id)
            })
            .map_err(|_| Status::InternalServerError)?;

        Argon2::default()
            .verify_password(password.as_bytes(), &parsed_hash)
            .map_err(|_| Status::Unauthorized)
    }

    pub async fn set_display_name(
        &mut self,
        display_name: Option<String>,
        db: &mut Connection<Postgres>,
    ) -> Result<(), sqlx::Error> {
        self.display_name = display_name;
        sqlx::query!(
            "UPDATE users SET display_name = $1 WHERE id = $2",
            self.display_name,
            self.id
        )
        .execute(&mut ***db)
        .await?;
        Ok(())
    }

    pub async fn set_username(
        &mut self,
        username: String,
        db: &mut Connection<Postgres>,
    ) -> Result<(), sqlx::Error> {
        self.username = username;
        sqlx::query!(
            "UPDATE users SET username = $1 WHERE id = $2",
            self.username,
            self.id
        )
        .execute(&mut ***db)
        .await?;
        Ok(())
    }

    pub async fn set_twofa_enabled(
        &mut self,
        enabled: bool,
        db: &mut Connection<Postgres>,
    ) -> Result<(), sqlx::Error> {
        self.twofa_enabled = enabled;
        sqlx::query!(
            "UPDATE users SET twofa_enabled = $1 WHERE id = $2",
            self.twofa_enabled,
            self.id
        )
        .execute(&mut ***db)
        .await?;
        Ok(())
    }

    pub async fn set_pass_hash(
        &mut self,
        pass_hash: String,
        db: &mut Connection<Postgres>,
    ) -> Result<(), sqlx::Error> {
        self.pass_hash = pass_hash;
        sqlx::query!(
            "UPDATE users SET pass_hash = $1 WHERE id = $2",
            self.pass_hash,
            self.id
        )
        .execute(&mut ***db)
        .await?;
        Ok(())
    }
}

#[get("/users", rank = 2)]
pub async fn users(_ag: Session, mut db: Connection<Postgres>) -> Json<Vec<i32>> {
    sqlx::query!("SELECT id FROM users")
        .fetch_all(&mut **db)
        .await
        .unwrap_or_else(|_| Vec::new())
        .into_iter()
        .map(|row| row.id)
        .collect::<Vec<i32>>()
        .into()
}

#[get("/users/<id>", rank = 1)]
pub async fn display_name(
    id: usize,
    _ag: Session,
    mut pgsql_conn: Connection<Postgres>,
    mut redis_conn: Connection<Redis>,
) -> String {
    UserCache::username(id, &mut redis_conn, &mut pgsql_conn).await
}

pub struct UserCache {}

impl UserCache {
    pub async fn username(
        id: usize,
        redis_conn: &mut Connection<Redis>,
        pgsql_conn: &mut Connection<Postgres>,
    ) -> String {
        if let Ok(val) = redis_conn.get(format!("users:{id}")).await {
            return val;
        }

        if let Ok(v) = sqlx::query!("SELECT username FROM users WHERE id = $1", id as i32)
            .fetch_one(&mut ***pgsql_conn)
            .await
        {
            let username = v.username;
            Self::insert(id, &username, redis_conn).await;
            username
        } else {
            unimplemented!()
        }
    }

    pub async fn insert(id: usize, username: &str, conn: &mut Connection<Redis>) {
        conn.set_ex::<_, _, ()>(format!("users:{id}"), username.to_string(), 1800)
            .await
            .expect("failed to insert key");
    }
}
