use argon2::{
    Argon2,
    password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
};
use jiff::Timestamp;
use tower_cookies::{Cookie, cookie::SameSite};

use crate::{
    constants::SESSION_COOKIE,
    error::CmsError,
    types::{
        cookie::CookieOptions,
        password::{Password, PasswordHash as CratePasswordHash},
    },
};

pub fn now() -> Timestamp {
    Timestamp::now()
}

pub(crate) fn hash_password(password: &Password) -> Result<CratePasswordHash, CmsError> {
    let salt = SaltString::generate(&mut OsRng);

    let argon2 = Argon2::default();

    // password_hash is a PHC string, where all the information like used algo, version, salt etc. is stored within the string.
    // # ${algorithm}${algorithm version}${$-separated algorithm parameters}${hash}${salt}
    // $argon2id$v=19$m=65536,t=2,p=1$gZiV/M1gPc22ElAH/Jh1Hw$CWOrkoo7oJBQ/iyh7uJ0LO2aLEfrHwTWllSAxT0zRno
    let password_hash = argon2
        .hash_password(password.password().as_bytes(), &salt)?
        .to_string();

    Ok(CratePasswordHash(password_hash))
}

pub(crate) async fn verify_password(
    password: Password,
    expected_password_hash: CratePasswordHash,
) -> Result<(), CmsError> {
    tokio::task::spawn_blocking(move || -> Result<(), CmsError> {
        let argon2 = Argon2::default();
        // TODO: get password hash directly as param instead of &str
        let expected_password_hash = PasswordHash::new(&expected_password_hash.0)?;
        argon2.verify_password(password.password().as_bytes(), &expected_password_hash)?;

        Ok(())
    })
    .await??;

    Ok(())
}

pub(crate) fn create_session_cookie(
    session_id: String,
    cookie_options: CookieOptions,
) -> Cookie<'static> {
    let mut cookie = Cookie::new(SESSION_COOKIE, session_id);

    cookie.set_http_only(true);
    cookie.set_path(cookie_options.path);
    cookie.set_same_site(SameSite::Lax);
    cookie.set_domain(cookie_options.domain);
    cookie.set_secure(cookie_options.secure);
    cookie.set_max_age(cookie_options.max_age);

    cookie
}

pub(crate) fn reset_session_cookie(mut cookie: Cookie, cookie_options: CookieOptions) -> Cookie {
    cookie.set_http_only(true);
    cookie.set_path(cookie_options.path);
    cookie.set_same_site(SameSite::Lax);
    cookie.set_domain(cookie_options.domain);
    cookie.set_secure(cookie_options.secure);
    cookie.set_max_age(cookie_options.max_age);

    cookie
}
