use anyhow::{Context, Result};
use chrono::{TimeDelta, Utc};
use deadpool_diesel::postgres::Pool;
use diesel::prelude::*;
use uuid::Uuid;

use crate::{app_error::AppError, models::CsrfToken, schema};

const TOKEN_PREFIX: &'static str = "csrf__";
const TTL_SEC: i64 = 60 * 60 * 24 * 7;

pub async fn generate_csrf_token_for_user(
    db_pool: &Pool,
    uid: Option<Uuid>,
) -> Result<String, AppError> {
    let id = Uuid::new_v4();
    let expires_at =
        Utc::now() + TimeDelta::new(TTL_SEC, 0).context("Failed to generate TimeDelta")?;
    db_pool
        .get()
        .await?
        .interact(move |conn| {
            diesel::insert_into(schema::csrf_tokens::table)
                .values((
                    schema::csrf_tokens::id.eq(id),
                    schema::csrf_tokens::user_id.eq(uid),
                    schema::csrf_tokens::expires_at.eq(expires_at),
                ))
                .execute(conn)
        })
        .await
        .unwrap()?;
    Ok(format!("{}{}", TOKEN_PREFIX, id.hyphenated().to_string()))
}

pub async fn validate_csrf_token_for_user(
    db_pool: &Pool,
    token: &str,
    uid: Option<Uuid>,
) -> Result<bool, AppError> {
    let id = match Uuid::try_parse(&token[TOKEN_PREFIX.len()..]) {
        Ok(id) => id,
        Err(_) => return Ok(false),
    };
    let row = db_pool
        .get()
        .await?
        .interact(move |conn| {
            schema::csrf_tokens::table
                .select(CsrfToken::as_select())
                .filter(schema::csrf_tokens::id.eq(id))
                .filter(schema::csrf_tokens::expires_at.gt(Utc::now()))
                .filter(schema::csrf_tokens::user_id.is_not_distinct_from(uid))
                .first(conn)
                .optional()
        })
        .await
        .unwrap()?;
    Ok(row.is_some())
}
