use klauthed_macros::DomainError;

/// Errors raised by the security primitives.
///
/// The `DomainError` impl (category + `security.<reason>` code) is generated by
/// the derive. Categories are chosen so HTTP/retry handling is sensible:
///
/// * malformed caller input (a token that isn't even well-formed) →
///   `bad_request`;
/// * an invalid / expired / not-yet-valid token, or one failing
///   issuer/audience checks → `unauthorized`;
/// * an internal cryptographic / key / RNG fault → `internal`.
#[derive(Debug, DomainError)]
#[domain(prefix = "security", category = "internal")]
#[non_exhaustive]
pub enum SecurityError {
    /// Hashing or verifying a password failed at the cryptographic layer
    /// (not a wrong password — that surfaces as `Ok(false)`).
    #[domain(category = "internal", code = "hash")]
    Hash(String),

    /// A stored password-hash (PHC) string could not be parsed.
    #[domain(category = "bad_request", code = "invalid_hash")]
    InvalidHash(String),

    /// A signing/verification key could not be loaded or is invalid
    /// (e.g. a malformed RSA PEM).
    #[domain(category = "internal", code = "key")]
    Key(String),

    /// A token could not be encoded/signed.
    #[domain(category = "internal", code = "encode")]
    Encode(String),

    /// A token is malformed (not a parseable JWT, bad base64, etc.).
    #[domain(category = "bad_request", code = "malformed_token")]
    MalformedToken(String),

    /// A token's signature, issuer, audience, or not-before is invalid.
    #[domain(category = "unauthorized", code = "invalid_token")]
    InvalidToken(String),

    /// A token has expired.
    #[domain(category = "unauthorized", code = "expired_token")]
    ExpiredToken,

    /// Computing a token's expiry overflowed the representable time range — the
    /// configured TTL is implausibly large. The string names the token kind
    /// (e.g. `"refresh token"`). A server misconfiguration, not caller input.
    #[domain(category = "internal", code = "token_ttl_overflow")]
    TokenTtlOverflow(String),

    /// The OS CSPRNG failed to produce random bytes.
    #[domain(category = "internal", code = "rng")]
    Rng,

    /// A session was looked up but does not exist (or was deleted).
    #[domain(category = "not_found", code = "session_not_found")]
    SessionNotFound,

    /// A session exists but has passed its `expires_at` instant.
    #[domain(category = "unauthorized", code = "session_expired")]
    SessionExpired,

    /// The principal is authenticated but lacks the required permission.
    #[domain(category = "forbidden", code = "forbidden")]
    Forbidden,

    /// An MFA (TOTP) secret or configuration could not be built (e.g. a secret
    /// shorter than RFC 4226's 128-bit minimum, or a bad provisioning label).
    #[domain(category = "internal", code = "mfa_config")]
    MfaConfig(String),

    /// A submitted MFA (TOTP) code did not match within the allowed time window.
    #[domain(category = "unauthorized", code = "invalid_mfa_code")]
    InvalidMfaCode,

    /// Authenticated symmetric encryption (AES-256-GCM sealing) failed at the
    /// cryptographic layer — an internal fault, never caller-controlled.
    #[domain(category = "internal", code = "encryption")]
    Encryption,

    /// Authenticated decryption failed: the ciphertext was too short to carry a
    /// nonce + tag, was not valid base64, or — most importantly — failed the
    /// GCM authentication tag check (tampered ciphertext, wrong key, or wrong
    /// AAD). Treated as caller-facing bad input rather than an internal fault.
    #[domain(category = "bad_request", code = "decryption")]
    Decryption,

    /// HKDF key derivation failed (e.g. a requested output length that exceeds
    /// HKDF's `255 * HashLen` ceiling).
    #[domain(category = "internal", code = "key_derivation")]
    KeyDerivation,

    /// A WebAuthn relying-party could not be configured (e.g. an invalid RP id
    /// or origin URL). A server misconfiguration, not caller input.
    #[domain(category = "internal", code = "webauthn_config")]
    WebauthnConfig(String),

    /// A WebAuthn registration or authentication ceremony failed to verify
    /// (bad client response, challenge mismatch, failed attestation, …).
    #[domain(category = "unauthorized", code = "webauthn")]
    Webauthn(String),

    /// A Have I Been Pwned breach-check request failed (external service).
    #[domain(category = "unavailable", code = "hibp")]
    Hibp(String),
}

impl std::fmt::Display for SecurityError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SecurityError::Hash(msg) => write!(f, "password hashing failed: {msg}"),
            SecurityError::InvalidHash(msg) => write!(f, "invalid password hash: {msg}"),
            SecurityError::Key(msg) => write!(f, "invalid or unloadable key: {msg}"),
            SecurityError::Encode(msg) => write!(f, "token encoding failed: {msg}"),
            SecurityError::MalformedToken(msg) => write!(f, "malformed token: {msg}"),
            SecurityError::InvalidToken(msg) => write!(f, "invalid token: {msg}"),
            SecurityError::ExpiredToken => f.write_str("token has expired"),
            SecurityError::TokenTtlOverflow(kind) => {
                write!(f, "{kind} ttl overflowed the representable time range")
            }
            SecurityError::Rng => f.write_str("the OS random number generator failed"),
            SecurityError::SessionNotFound => f.write_str("session not found"),
            SecurityError::SessionExpired => f.write_str("session has expired"),
            SecurityError::Forbidden => f.write_str("not authorized for the requested action"),
            SecurityError::MfaConfig(msg) => write!(f, "invalid MFA configuration: {msg}"),
            SecurityError::InvalidMfaCode => f.write_str("invalid MFA code"),
            SecurityError::Encryption => f.write_str("authenticated encryption failed"),
            SecurityError::Decryption => {
                f.write_str("authenticated decryption failed (tampered, wrong key, or wrong AAD)")
            }
            SecurityError::KeyDerivation => f.write_str("HKDF key derivation failed"),
            SecurityError::WebauthnConfig(msg) => {
                write!(f, "invalid WebAuthn configuration: {msg}")
            }
            SecurityError::Webauthn(msg) => write!(f, "WebAuthn ceremony failed: {msg}"),
            SecurityError::Hibp(msg) => write!(f, "breach check failed: {msg}"),
        }
    }
}

impl std::error::Error for SecurityError {}
