from __future__ import annotations

import hashlib
import secrets
from base64 import urlsafe_b64decode, urlsafe_b64encode

PBKDF2_ALGORITHM = "sha256"
PBKDF2_ITERATIONS = 120_000
SALT_BYTES = 16


def hash_password(password: str) -> str:
    salt = secrets.token_bytes(SALT_BYTES)
    digest = hashlib.pbkdf2_hmac(
        PBKDF2_ALGORITHM,
        password.encode("utf-8"),
        salt,
        PBKDF2_ITERATIONS,
    )
    encoded_salt = urlsafe_b64encode(salt).decode("utf-8")
    encoded_digest = urlsafe_b64encode(digest).decode("utf-8")
    return f"pbkdf2_{PBKDF2_ALGORITHM}${PBKDF2_ITERATIONS}${encoded_salt}${encoded_digest}"


def verify_password(password: str, password_hash: str) -> bool:
    if password_hash.startswith("scrypt$"):
        return verify_scrypt_password(password, password_hash)

    try:
        scheme, iterations, encoded_salt, encoded_digest = password_hash.split("$", 3)
    except ValueError:
        return False

    if scheme != f"pbkdf2_{PBKDF2_ALGORITHM}":
        return False

    salt = urlsafe_b64decode(encoded_salt.encode("utf-8"))
    expected_digest = urlsafe_b64decode(encoded_digest.encode("utf-8"))
    computed_digest = hashlib.pbkdf2_hmac(
        PBKDF2_ALGORITHM,
        password.encode("utf-8"),
        salt,
        int(iterations),
    )
    return secrets.compare_digest(computed_digest, expected_digest)


def verify_scrypt_password(password: str, password_hash: str) -> bool:
    try:
        scheme, n_value, r_value, p_value, key_length, salt_hex, derived_key_hex = password_hash.split("$", 6)
    except ValueError:
        return False

    if scheme != "scrypt":
        return False

    try:
        salt = bytes.fromhex(salt_hex)
        expected_key = bytes.fromhex(derived_key_hex)
        derived_key = hashlib.scrypt(
            password.encode("utf-8"),
            salt=salt,
            n=int(n_value),
            r=int(r_value),
            p=int(p_value),
            dklen=int(key_length),
        )
    except ValueError:
        return False

    return secrets.compare_digest(derived_key, expected_key)
