// Copyright 2026 Thomas Zuyev

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[cfg(feature = "encryption")]
use crate::crypto::{CipherSuite, EncryptedSecret};

use crate::{random, range::LengthRange};
use argon2::{PasswordHasher, PasswordVerifier, password_hash::SaltString};
use std::num::NonZeroU32;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[cfg(feature = "encryption")]
    #[error("Crypto error: {0}")]
    Crypto(#[from] crate::crypto::Error),

    #[error("Random error: {0}")]
    Random(#[from] crate::random::Error),
    #[error("Argon2 hash error: {0}")]
    Argon2HashError(argon2::password_hash::Error),
    #[error("Hash algorithm is missing")]
    HashAlgorithmMissing,
    #[error("Hash algorithm is unknown or invalid: {0}")]
    HashAlgorithmInvalid(u8),
    #[error("Invalid UTF-8: {0}")]
    Utf8(#[from] std::str::Utf8Error),
    #[error("Password alphabet is empty. At least one alphabet should be enabled")]
    EmptyAlphabet,
    #[error("Specified length range is invalid: {0}")]
    InvalidRange(LengthRange),
    #[error("Min length specified in policy does not allow all alphabets to be present")]
    ImpossiblePolicy,
    #[error("Length mismatch: expected = {expected}; provided = {provided}")]
    LengthMismatch {
        expected: LengthRange,
        provided: usize,
    },
    #[error("Digits are required but were not found")]
    DigitsNotFound,
    #[error("Lowercase symbols are required but were not found")]
    LowercaseNotFound,
    #[error("Uppercase symbols are required but were not found")]
    UppercaseNotFound,
    #[error("Special symbols are required but were not found")]
    SpecialNotFound,
}

impl From<argon2::password_hash::Error> for Error {
    fn from(value: argon2::password_hash::Error) -> Self {
        Self::Argon2HashError(value)
    }
}

pub type Result<T> = std::result::Result<T, Error>;

/// Password container that automatically zeroes its memory on drop.
///
/// Using zeroization ensures the password is removed from memory
/// when the object is dropped, reducing the risk of leaking sensitive data.
///
/// This type does NOT prevent copies from being created.
/// Cloning or formatting outside this type may leak data.
/// Zeroization only applies to this container.
#[derive(zeroize::ZeroizeOnDrop, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct Password(String);

impl Password {
    /// Generates a cryptographically secure random password according to `PasswordConfig`.
    ///
    /// # Algorithm
    /// Uses OsRng as a secure random source. Chooses symbols from the alphabet
    /// using **rejection sampling** to avoid modulo bias, ensuring uniform distribution.
    ///
    /// If multiple character classes are required, generation may retry
    /// until all requirements are satisfied.
    ///
    /// # Errors
    /// - `Error::EmptyAlphabet` if the alphabet is empty.
    /// - Propagates any RNG or memory allocation errors.
    #[must_use]
    pub fn random(policy: &PasswordPolicy) -> Result<Self> {
        let alph = policy.alphabet();
        let mut passw = String::with_capacity(policy.length().min());
        let alph_len = NonZeroU32::new(alph.len() as u32).ok_or(Error::EmptyAlphabet)?;

        // If policy requires multiple alphabets, ensure all of them
        // are present in password. This slightly decreases entropy
        // but passwords with only a few alphabets will not pass the
        // policy validation and maybe too "weak" to be used
        loop {
            while passw.len() < policy.length().min() {
                let idx = random::secure_idx(alph_len)?;
                passw.push(alph[idx] as char);
            }

            if policy.validate(&passw).is_ok() {
                return Ok(Self(passw));
            } else {
                passw.clear();
            }
        }
    }

    pub fn as_bytes(&self) -> &[u8] {
        self.0.as_bytes()
    }

    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }

    /// Caller must ensure secrecy of input and manual zeroization
    #[must_use]
    pub fn from_string(data: String) -> Self {
        Self(data)
    }

    #[cfg(feature = "encryption")]
    #[must_use]
    pub fn encrypt(&self, key: &[u8], algo: CipherSuite) -> Result<EncryptedPassword> {
        let enc = EncryptedSecret::encrypt(self.as_bytes(), key, algo)?;
        Ok(EncryptedPassword::from_secret(enc))
    }

    /// Convenience method to hash the password using the default algorithm (Argon2id)
    #[must_use]
    pub fn to_default_hash(&self) -> Result<PasswordHash> {
        self.to_default_argon2()
    }

    /// Hashes the password using Argon2id with a random salt.
    ///
    /// # Security Notes
    /// - Uses Argon2id default parameters (safe defaults recommended by the library).
    /// - Each call generates a new random salt, producing a different hash even for the same password.
    ///
    /// # Errors
    /// Propagates errors from the Argon2 library (e.g., hashing failure, memory issues).
    #[must_use]
    pub fn to_default_argon2(&self) -> Result<PasswordHash> {
        let salt = SaltString::generate(&mut argon2::password_hash::rand_core::OsRng);
        let h = argon2::Argon2::default()
            .hash_password(self.as_bytes(), &salt)?
            .to_string();

        Ok(PasswordHash::Argon2id(h))
    }
}

impl std::fmt::Debug for Password {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "<redacted>")
    }
}

impl std::fmt::Display for Password {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "<redacted>")
    }
}

const ALG_ARGON2ID_V1: u8 = 0;

/// Enum representing supported password hashes
#[derive(zeroize::ZeroizeOnDrop, Clone)]
pub enum PasswordHash {
    Argon2id(String),
}

impl PasswordHash {
    /// Serializes the password hash into bytes.
    /// Format: `[algorithm_id (1 byte)] + [hash bytes]`
    pub fn to_bytes(&self) -> Vec<u8> {
        match &self {
            Self::Argon2id(h) => {
                let mut res = Vec::with_capacity(1 + 64);
                res.push(ALG_ARGON2ID_V1);
                res.extend_from_slice(h.as_bytes());
                res
            }
        }
    }

    /// Deserializes a password hash from bytes.
    ///
    /// # Errors
    /// - `Error::HashAlgorithmMissing` if input is empty.
    /// - `Error::HashAlgorithmInvalid(algo)` if the first byte is unrecognized.
    /// - UTF-8 errors if the hash bytes are invalid UTF-8
    /// (in case algorithm operates on strings like Argon2).
    #[must_use]
    pub fn from_bytes(data: &[u8]) -> Result<Self> {
        if data.len() < 1 {
            return Err(Error::HashAlgorithmMissing);
        }

        match data[0] {
            ALG_ARGON2ID_V1 => Ok(Self::Argon2id(str::from_utf8(&data[1..])?.into())),
            algo => Err(Error::HashAlgorithmInvalid(algo)),
        }
    }

    /// Verifies a password against the stored hash.
    ///
    /// # Errors
    /// - Returns any errors from Argon2 (invalid hash format, mismatch, etc.).
    /// - On success, returns `Ok(())`; otherwise an error indicates verification failed.
    #[must_use]
    pub fn verify(&self, passw: &Password) -> Result<()> {
        match self {
            PasswordHash::Argon2id(h) => {
                let h = argon2::PasswordHash::new(&h)?;
                argon2::Argon2::default().verify_password(passw.as_bytes(), &h)?;
                Ok(())
            }
        }
    }
}

impl std::fmt::Debug for PasswordHash {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "<redacted>")
    }
}

impl std::fmt::Display for PasswordHash {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "<redacted>")
    }
}

/// SQLx mapping for Postgres `BYTEA`.
///
/// This allows `PasswordHash` to be used directly in `FromRow` structs
/// and query parameters without exposing its internal format.
#[cfg(feature = "sqlx")]
mod sqlx_impl {
    use super::*;
    use sqlx::{Database, Decode, Encode, Type, encode::IsNull, error::BoxDynError};

    #[cfg(feature = "sqlx_postgres")]
    impl sqlx::postgres::PgHasArrayType for PasswordHash {
        fn array_type_info() -> sqlx::postgres::PgTypeInfo {
            // Under the hood, we encode this as a byte array,
            // so our Postgres array type is an array of BYTEA.
            <Vec<u8> as sqlx::postgres::PgHasArrayType>::array_type_info()
        }
    }

    impl<'a, DB: Database> Type<DB> for PasswordHash
    where
        &'a [u8]: Type<DB>,
    {
        fn type_info() -> <DB as Database>::TypeInfo {
            <&[u8] as Type<DB>>::type_info()
        }

        fn compatible(ty: &<DB as Database>::TypeInfo) -> bool {
            <&[u8] as Type<DB>>::compatible(ty)
        }
    }

    impl<'a, DB: Database> Encode<'a, DB> for PasswordHash
    where
        Vec<u8>: Encode<'a, DB>,
    {
        fn encode_by_ref(
            &self,
            buf: &mut <DB as Database>::ArgumentBuffer<'a>,
        ) -> std::result::Result<IsNull, BoxDynError> {
            <Vec<u8> as Encode<'a, DB>>::encode_by_ref(&self.to_bytes(), buf)
        }
        fn produces(&self) -> Option<<DB as Database>::TypeInfo> {
            <Vec<u8> as Encode<'a, DB>>::produces(&self.to_bytes())
        }
        fn size_hint(&self) -> usize {
            <Vec<u8> as Encode<'a, DB>>::size_hint(&self.to_bytes())
        }
    }

    impl<'a, DB: Database> Decode<'a, DB> for PasswordHash
    where
        Vec<u8>: Decode<'a, DB>,
    {
        fn decode(value: <DB as Database>::ValueRef<'a>) -> std::result::Result<Self, BoxDynError> {
            Ok(Self::from_bytes(&<Vec<u8> as Decode<'a, DB>>::decode(
                value,
            )?)?)
        }
    }
}

// This type is only compiled when encryption support is enabled.
// It allows building the crate without pulling crypto dependencies.
#[cfg(feature = "encryption")]
// When serde is enabled, serialize/deserialize transparently as the inner type.
// This means the serialized form is identical to `EncryptedSecret`.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
// When sqlx is enabled, map this type directly to the same database type
// as `EncryptedSecret` (typically a binary column).
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[cfg_attr(feature = "sqlx", sqlx(transparent))]
#[derive(Clone, Debug)]
/// Newtype wrapper around `EncryptedSecret`.
///
/// This provides strong type separation between different encrypted domain values.
/// Even though it is the same underlying representation, this prevents accidental
/// mixing of encrypted passwords with other encrypted secrets at the type level.
///
/// The tuple struct keeps zero runtime overhead.
pub struct EncryptedPassword(EncryptedSecret);

#[cfg(feature = "encryption")]
impl EncryptedPassword {
    /// Construct from serialized bytes.
    ///
    /// Expected layout:
    ///     [ algorithm (1 byte) | nonce | ciphertext | tag ]
    ///
    /// This does not validate the key or decrypt the content.
    /// It only reconstructs the encrypted container.
    #[must_use]
    pub fn from_encrypted_bytes(input: &[u8]) -> Result<Self> {
        let secret = EncryptedSecret::from_encrypted_bytes(input)?;
        Ok(Self(secret))
    }

    #[must_use]
    pub fn from_secret(secret: EncryptedSecret) -> Self {
        Self(secret)
    }

    /// Serialize into stable binary representation.
    ///
    /// Output layout:
    ///     [ algorithm (1 byte) | payload... ]
    ///
    /// This is suitable for:
    /// - database storage
    /// - network transport
    /// - file persistence
    #[must_use]
    pub fn to_encrypted_bytes(&self) -> Vec<u8> {
        self.0.to_encrypted_bytes()
    }

    /// Decrypt the password using the provided key.
    ///
    /// Steps:
    /// 1. Perform AEAD decryption and authentication verification.
    /// 2. Interpret decrypted bytes as UTF-8.
    /// 3. Convert into domain `Password` type.
    ///
    /// Failure cases:
    /// - Wrong key
    /// - Tampered ciphertext
    /// - Invalid UTF-8
    ///
    /// UTF-8 validation ensures that encrypted passwords are always valid strings,
    /// preventing propagation of arbitrary binary data into the domain layer.
    #[must_use]
    pub fn decrypt(&self, key: &[u8]) -> Result<Password> {
        let plaintext = self.0.decrypt(key)?;

        // Enforce UTF-8 correctness at the boundary.
        // If corrupted or malicious data decrypts into invalid UTF-8,
        // we surface the decoding error.
        let s = String::from_utf8(plaintext).map_err(|e| e.utf8_error())?;

        // Convert into strong domain type instead of returning raw String.
        Ok(Password::from_string(s))
    }

    /// Returns which cipher suite was used to encrypt this password.
    ///
    /// This is metadata only and does not reveal the password.
    pub fn algorithm(&self) -> CipherSuite {
        self.0.algorithm()
    }
}

#[cfg(feature = "encryption")]
impl std::fmt::Display for EncryptedPassword {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Builder for [`PasswordPolicy`].
///
/// Allows configuring:
/// - Allowed password length range
/// - Required character classes (lowercase, uppercase, digits, special)
///
/// `build()` fails if no character class is enabled, because a policy
/// without any enabled alphabet would be unusable for generation.
#[derive(Debug, Clone)]
pub struct PasswordPolicyBuilder {
    range: LengthRange,
    lowercase: bool,
    uppercase: bool,
    digits: bool,
    special: bool,
}

impl PasswordPolicyBuilder {
    pub fn length<R>(mut self, range: R) -> Self
    where
        R: std::ops::RangeBounds<usize>,
    {
        self.range = LengthRange::new(range);
        self
    }

    pub fn lowercase(mut self, enable: bool) -> Self {
        self.lowercase = enable;
        self
    }

    pub fn uppercase(mut self, enable: bool) -> Self {
        self.uppercase = enable;
        self
    }

    pub fn digits(mut self, enable: bool) -> Self {
        self.digits = enable;
        self
    }

    pub fn special(mut self, enable: bool) -> Self {
        self.special = enable;
        self
    }

    /// Finalizes the builder and returns a [`PasswordPolicy`].
    ///
    /// # Errors
    ///
    /// Returns [`Error::EmptyAlphabet`] if all character classes
    /// (lowercase, uppercase, digits, special) are disabled.
    pub fn build(self) -> Result<PasswordPolicy> {
        let alph_total = self.lowercase as usize
            + self.uppercase as usize
            + self.digits as usize
            + self.special as usize;

        if alph_total == 0 {
            return Err(Error::EmptyAlphabet);
        }

        if self
            .range
            .max()
            .map(|max| max < self.range.min())
            .unwrap_or(false)
        {
            return Err(Error::InvalidRange(self.range));
        }

        if self.range.min() < alph_total {
            return Err(Error::ImpossiblePolicy);
        }

        Ok(PasswordPolicy {
            range: self.range,
            lowercase: self.lowercase,
            uppercase: self.uppercase,
            digits: self.digits,
            special: self.special,
        })
    }
}

/// Password policy configuration.
///
/// Defines:
/// - Allowed password length range
/// - Which character classes are required
///
/// The policy is used both for:
/// - Password generation (via `alphabet()`)
/// - Password validation (via `validate()`)
///
/// Note:
/// Validation does **not** restrict passwords to a predefined alphabet.
/// Any character outside `[a-zA-Z0-9]` is treated as a "special" character.
#[derive(Debug, Clone)]
pub struct PasswordPolicy {
    range: LengthRange,
    lowercase: bool,
    uppercase: bool,
    digits: bool,
    special: bool,
}

impl PasswordPolicy {
    /// Creates a builder preconfigured with [`with_sane_defaults`].
    ///
    /// This is the recommended starting point for most use cases.
    pub fn builder() -> PasswordPolicyBuilder {
        let defaults = Self::with_sane_defaults();
        PasswordPolicyBuilder {
            range: defaults.range,
            lowercase: defaults.lowercase,
            uppercase: defaults.uppercase,
            digits: defaults.digits,
            special: defaults.special,
        }
    }

    /// Returns a policy with commonly accepted practical defaults.
    ///
    /// Defaults:
    /// - Minimum length: 12 characters (no upper bound)
    /// - Lowercase letters: required
    /// - Uppercase letters: required
    /// - Digits: required
    /// - Special symbols: not required
    ///
    /// This configuration aims to provide good entropy while avoiding
    /// special characters, which are sometimes restricted by legacy systems.
    pub fn with_sane_defaults() -> Self {
        Self {
            range: LengthRange::new(12..),
            lowercase: true,
            uppercase: true,
            digits: true,
            special: false,
        }
    }

    pub fn length(&self) -> LengthRange {
        self.range
    }

    pub fn lowercase(&self) -> bool {
        self.lowercase
    }

    pub fn uppercase(&self) -> bool {
        self.uppercase
    }

    pub fn digits(&self) -> bool {
        self.digits
    }

    pub fn special(&self) -> bool {
        self.special
    }

    /// Builds the character set used for password generation
    /// according to enabled character classes.
    ///
    /// The returned vector contains ASCII bytes only.
    ///
    /// Note:
    /// This method assumes the policy was constructed via `builder()`
    /// or `build()`, which guarantees that at least one character class
    /// is enabled.
    pub fn alphabet(&self) -> Vec<u8> {
        let mut alph = Vec::new();

        if self.lowercase {
            alph.extend_from_slice(b"abcdefghijklmnopqrstuvwxyz");
        }
        if self.uppercase {
            alph.extend_from_slice(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
        }
        if self.digits {
            alph.extend_from_slice(b"0123456789");
        }
        if self.special {
            alph.extend_from_slice(b"!@#$%^&*()-_=+[]{};:,.<>/?");
        }

        alph
    }

    /// Validates a password against this policy.
    ///
    /// Checks:
    /// - Length is within configured range (measured in Unicode scalar values)
    /// - Required character classes are present at least once
    ///
    /// Character classification rules:
    /// - `'a'..='z'` → lowercase
    /// - `'A'..='Z'` → uppercase
    /// - `'0'..='9'` → digit
    /// - Any other character → special
    ///
    /// Validation is policy-oriented and does not estimate password strength.
    /// A valid password is not necessarily a strong password.
    ///
    /// Note:
    /// The validator does **not** restrict which special characters are allowed.
    /// Any non-alphanumeric character satisfies the "special" requirement.
    ///
    /// # Errors
    ///
    /// - [`Error::LengthMismatch`] if the length is outside the configured range
    /// - [`Error::UppercaseNotFound`] if uppercase is required but absent
    /// - [`Error::LowercaseNotFound`] if lowercase is required but absent
    /// - [`Error::DigitsNotFound`] if digits are required but absent
    /// - [`Error::SpecialNotFound`] if special characters are required but absent
    pub fn validate(&self, passw: &str) -> Result<()> {
        // Check what characters password contains
        let mut uppercase = 0;
        let mut lowercase = 0;
        let mut digits = 0;
        let mut special = 0;

        for c in passw.chars() {
            match c {
                'a'..='z' => lowercase += 1,
                'A'..='Z' => uppercase += 1,
                '0'..='9' => digits += 1,
                _ => special += 1,
            }
        }

        let len = uppercase + lowercase + digits + special;

        // Check length
        if !self.range.within(len) {
            return Err(Error::LengthMismatch {
                expected: self.range,
                provided: len,
            });
        }

        if self.uppercase && uppercase == 0 {
            return Err(Error::UppercaseNotFound);
        }
        if self.lowercase && lowercase == 0 {
            return Err(Error::LowercaseNotFound);
        }
        if self.digits && digits == 0 {
            return Err(Error::DigitsNotFound);
        }
        if self.special && special == 0 {
            return Err(Error::SpecialNotFound);
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn length() {
        let verifier = PasswordPolicy::builder().length(5..10).build().unwrap();

        assert!(matches!(
            verifier.validate("Ab1!"),
            Err(Error::LengthMismatch { .. })
        ));
        assert!(matches!(
            verifier.validate("Abcdef123456!@#$"),
            Err(Error::LengthMismatch { .. })
        ));
        assert!(verifier.validate("Abc123!").is_ok());
    }

    #[test]
    fn uppercase() {
        let verifier = PasswordPolicy::builder()
            .length(2..)
            .uppercase(true)
            .lowercase(false)
            .digits(false)
            .special(false)
            .build()
            .unwrap();

        assert!(matches!(
            verifier.validate("aaa"),
            Err(Error::UppercaseNotFound)
        ));
        assert!(verifier.validate("ABC").is_ok());
    }

    #[test]
    fn lowercase() {
        let verifier = PasswordPolicy::builder()
            .length(2..)
            .uppercase(false)
            .lowercase(true)
            .digits(false)
            .special(false)
            .build()
            .unwrap();

        assert!(matches!(
            verifier.validate("AAA"),
            Err(Error::LowercaseNotFound)
        ));
        assert!(verifier.validate("abc").is_ok());
    }

    #[test]
    fn digits() {
        let verifier = PasswordPolicy::builder()
            .length(2..)
            .uppercase(false)
            .lowercase(false)
            .digits(true)
            .special(false)
            .build()
            .unwrap();

        assert!(matches!(
            verifier.validate("AAA"),
            Err(Error::DigitsNotFound)
        ));
        assert!(verifier.validate("123").is_ok());
    }

    #[test]
    fn special() {
        let verifier = PasswordPolicy::builder()
            .length(2..)
            .uppercase(false)
            .lowercase(false)
            .digits(false)
            .special(true)
            .build()
            .unwrap();

        assert!(matches!(
            verifier.validate("AAA"),
            Err(Error::SpecialNotFound)
        ));
        assert!(verifier.validate("!@#$").is_ok());
    }

    #[test]
    fn defaults() {
        let verifier = PasswordPolicy::with_sane_defaults();

        assert!(verifier.validate("AytW416p75yV").is_ok());
        assert!(matches!(
            verifier.validate("Ab1!"),
            Err(Error::LengthMismatch { .. })
        ));
        assert!(matches!(
            verifier.validate("byr35dht8sbe6"),
            Err(Error::UppercaseNotFound)
        ));
    }

    #[test]
    fn impossible_policy() {
        let policy = PasswordPolicy::builder()
            .length(3..)
            .digits(true)
            .lowercase(true)
            .uppercase(true)
            .special(true)
            .build();

        assert!(matches!(policy, Err(Error::ImpossiblePolicy)));
    }

    #[test]
    fn invalid_range() {
        let policy = PasswordPolicy::builder().length(8..4).build();

        assert!(matches!(policy, Err(Error::InvalidRange(..))));
    }

    #[test]
    fn empty_alphabet() {
        let policy = PasswordPolicy::builder()
            .digits(false)
            .uppercase(false)
            .lowercase(false)
            .special(false)
            .build();

        assert!(matches!(policy, Err(Error::EmptyAlphabet)));
    }
}
