//! MFA Verification Flow
//!
//! Handles verification of MFA codes during authentication.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use uuid::Uuid;

/// MFA verification request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MfaVerification {
    /// User ID
    pub user_id: Uuid,
    /// Verification method
    pub method: VerificationMethod,
    /// Code to verify
    pub code: String,
    /// Client IP address (for audit)
    pub ip_address: Option<String>,
    /// User agent (for audit)
    pub user_agent: Option<String>,
}

/// Verification method
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VerificationMethod {
    /// TOTP code from authenticator app
    Totp,
    /// Backup recovery code
    BackupCode,
    /// Trusted device (future enhancement)
    TrustedDevice,
}

/// Verification result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationResult {
    /// Whether verification succeeded
    pub success: bool,
    /// User ID
    pub user_id: Uuid,
    /// Verification method used
    pub method: VerificationMethod,
    /// Timestamp of verification
    pub timestamp: DateTime<Utc>,
    /// Additional metadata
    pub metadata: VerificationMetadata,
}

/// Verification metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationMetadata {
    /// Number of failed attempts
    pub failed_attempts: i32,
    /// Whether account is locked
    pub is_locked: bool,
    /// Lock expiration (if locked)
    pub locked_until: Option<DateTime<Utc>>,
    /// Remaining backup codes (if applicable)
    pub backup_codes_remaining: Option<i32>,
    /// Time drift in TOTP verification (if applicable)
    pub totp_drift: Option<i32>,
}

impl VerificationResult {
    /// Create successful verification result
    pub fn success(
        user_id: Uuid,
        method: VerificationMethod,
        metadata: VerificationMetadata,
    ) -> Self {
        Self {
            success: true,
            user_id,
            method,
            timestamp: Utc::now(),
            metadata,
        }
    }

    /// Create failed verification result
    pub fn failure(
        user_id: Uuid,
        method: VerificationMethod,
        metadata: VerificationMetadata,
    ) -> Self {
        Self {
            success: false,
            user_id,
            method,
            timestamp: Utc::now(),
            metadata,
        }
    }
}

/// Verification errors
#[derive(Debug, Error)]
pub enum VerificationError {
    #[error("MFA not configured for user")]
    NotConfigured,

    #[error("MFA is not enabled for user")]
    NotEnabled,

    #[error("Account is locked due to too many failed attempts")]
    AccountLocked,

    #[error("Invalid verification code")]
    InvalidCode,

    #[error("Verification code has expired")]
    CodeExpired,

    #[error("Backup code already used")]
    BackupCodeUsed,

    #[error("No backup codes remaining")]
    NoBackupCodesRemaining,

    #[error("Database error: {0}")]
    DatabaseError(String),

    #[error("Internal error: {0}")]
    InternalError(String),
}

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

    #[test]
    fn test_verification_result_success() {
        let user_id = Uuid::new_v4();
        let metadata = VerificationMetadata {
            failed_attempts: 0,
            is_locked: false,
            locked_until: None,
            backup_codes_remaining: Some(10),
            totp_drift: None,
        };

        let result = VerificationResult::success(user_id, VerificationMethod::Totp, metadata);

        assert!(result.success);
        assert_eq!(result.user_id, user_id);
        assert_eq!(result.method, VerificationMethod::Totp);
        assert_eq!(result.metadata.failed_attempts, 0);
    }

    #[test]
    fn test_verification_result_failure() {
        let user_id = Uuid::new_v4();
        let metadata = VerificationMetadata {
            failed_attempts: 3,
            is_locked: false,
            locked_until: None,
            backup_codes_remaining: Some(10),
            totp_drift: None,
        };

        let result = VerificationResult::failure(user_id, VerificationMethod::Totp, metadata);

        assert!(!result.success);
        assert_eq!(result.metadata.failed_attempts, 3);
    }

    #[test]
    fn test_verification_method_serialization() {
        let method = VerificationMethod::Totp;
        let json = serde_json::to_string(&method).expect("INVARIANT: Serialization should succeed for valid types");
        assert_eq!(json, "\"totp\"");

        let method = VerificationMethod::BackupCode;
        let json = serde_json::to_string(&method).expect("INVARIANT: Serialization should succeed for valid types");
        assert_eq!(json, "\"backup_code\"");
    }
}
