use chrono::{NaiveDateTime, TimeDelta};
use sqlx::{query_as, PgPool};

use super::Result;
use crate::utils::token_generation::generate_token_and_expiration;

pub trait Token {
    async fn delete(&self, pool: &PgPool) -> Result<()>;
}

pub struct NoneToken {}
impl Token for NoneToken {
    async fn delete(&self, _pool: &PgPool) -> Result<()> {
        unimplemented!()
    }
}

#[derive(Debug)]
pub struct PasswordReset {
    pub id: i32,
    pub token: String,
    pub userid: i32,
    pub expires: NaiveDateTime,
}

impl PasswordReset {
    pub async fn insert_new_for_user(pool: &PgPool, user_id: i32) -> Result<PasswordReset> {
        let (token, expires) = generate_token_and_expiration(64, TimeDelta::hours(24));

        let inserted = query_as!(
            PasswordReset,
            "INSERT INTO passwordReset (token, userId, expires) VALUES ($1, $2, $3) RETURNING *;",
            token,
            user_id,
            expires
        )
        .fetch_one(pool)
        .await?;

        Ok(inserted)
    }

    pub async fn does_token_exist(pool: &PgPool, token: &str) -> Result<Option<PasswordReset>> {
        let result = query_as!(
            PasswordReset,
            "SELECT * FROM passwordReset WHERE token = $1 AND expires > NOW();",
            token
        )
        .fetch_optional(pool)
        .await?;

        Ok(result)
    }

    pub async fn delete(pool: &PgPool, token: &str) -> Result<()> {
        sqlx::query!("DELETE FROM passwordReset WHERE token = $1;", token)
            .execute(pool)
            .await?;

        Ok(())
    }
}

impl Token for PasswordReset {
    async fn delete(&self, pool: &PgPool) -> Result<()> {
        PasswordReset::delete(pool, &self.token).await
    }
}
