"""
Cryptographic utilities for secrets management.

Uses Argon2id for password-based key derivation and Fernet for encryption.
"""
import os
import secrets as secrets_module
from typing import Tuple

from argon2 import PasswordHasher
from argon2.low_level import hash_secret_raw, Type
from cryptography.fernet import Fernet
import base64


# Argon2id parameters (OWASP recommended for password-based KDF)
# These provide strong defense against GPU/ASIC attacks
ARGON2_TIME_COST = 3  # iterations
ARGON2_MEMORY_COST = 65536  # 64 MB
ARGON2_PARALLELISM = 4  # threads
ARGON2_HASH_LENGTH = 32  # bytes (256 bits for Fernet key)
ARGON2_SALT_LENGTH = 16  # bytes (128 bits)


def generate_salt() -> bytes:
    """Generate a cryptographically secure random salt."""
    return secrets_module.token_bytes(ARGON2_SALT_LENGTH)


def derive_key_from_password(password: str, salt: bytes) -> bytes:
    """
    Derive an encryption key from a password using Argon2id.

    Args:
        password: The master password
        salt: The salt (must be consistent for the same password to work)

    Returns:
        32-byte key suitable for Fernet encryption
    """
    password_bytes = password.encode('utf-8')

    # Use Argon2id (hybrid mode - best of Argon2i and Argon2d)
    raw_hash = hash_secret_raw(
        secret=password_bytes,
        salt=salt,
        time_cost=ARGON2_TIME_COST,
        memory_cost=ARGON2_MEMORY_COST,
        parallelism=ARGON2_PARALLELISM,
        hash_len=ARGON2_HASH_LENGTH,
        type=Type.ID  # Argon2id
    )

    return raw_hash


def create_fernet(key: bytes) -> Fernet:
    """
    Create a Fernet cipher instance from a raw key.

    Args:
        key: 32-byte raw key from Argon2id

    Returns:
        Fernet instance for encryption/decryption
    """
    # Fernet requires a URL-safe base64-encoded 32-byte key
    fernet_key = base64.urlsafe_b64encode(key)
    return Fernet(fernet_key)


def encrypt_data(data: bytes, key: bytes) -> bytes:
    """
    Encrypt data using Fernet (AES-256-CBC).

    Args:
        data: Raw bytes to encrypt
        key: 32-byte encryption key

    Returns:
        Encrypted data (includes IV and auth tag)
    """
    fernet = create_fernet(key)
    return fernet.encrypt(data)


def decrypt_data(encrypted_data: bytes, key: bytes) -> bytes:
    """
    Decrypt data using Fernet.

    Args:
        encrypted_data: Encrypted bytes from encrypt_data
        key: 32-byte encryption key (must match encryption key)

    Returns:
        Decrypted raw bytes

    Raises:
        cryptography.fernet.InvalidToken: If decryption fails (wrong key/corrupted data)
    """
    fernet = create_fernet(key)
    return fernet.decrypt(encrypted_data)


def create_verification_hash(password: str, salt: bytes) -> str:
    """
    Create a verification hash to check if a password is correct.

    This is NOT for storing the password - it's for verifying the password
    unlocks the correct key without trying to decrypt the entire secrets file.

    Args:
        password: The master password
        salt: The salt used for key derivation

    Returns:
        Base64-encoded hash for verification
    """
    # Derive key and hash it again for verification
    key = derive_key_from_password(password, salt)

    # Simple hash of the key for verification (not security critical since
    # the key itself is already derived from Argon2id)
    verification = base64.b64encode(key[:16]).decode('ascii')

    return verification


def verify_password(password: str, salt: bytes, verification_hash: str) -> bool:
    """
    Verify a password against a verification hash.

    Args:
        password: Password to verify
        salt: Salt used for key derivation
        verification_hash: Expected verification hash

    Returns:
        True if password is correct, False otherwise
    """
    computed_hash = create_verification_hash(password, salt)

    # Constant-time comparison to prevent timing attacks
    return secrets_module.compare_digest(computed_hash, verification_hash)
