//! MFA Enrollment Flow
//!
//! Handles the enrollment process for setting up multi-factor authentication.

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

/// Enrollment session for MFA setup
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnrollmentSession {
    /// Session ID
    pub session_id: Uuid,
    /// User ID
    pub user_id: Uuid,
    /// QR code URI (otpauth://)
    pub qr_code_uri: String,
    /// QR code as PNG image (base64 or bytes)
    pub qr_code_png: Vec<u8>,
    /// Manual entry key for authenticator apps
    pub manual_entry_key: String,
    /// Session expiration time
    pub expires_at: DateTime<Utc>,
}

/// MFA enrollment state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MfaEnrollment {
    /// User ID
    pub user_id: Uuid,
    /// Enrollment status
    pub status: EnrollmentStatus,
    /// Current session (if active)
    pub session: Option<EnrollmentSession>,
    /// Number of verification attempts
    pub verification_attempts: u32,
    /// Maximum allowed attempts
    pub max_attempts: u32,
}

impl MfaEnrollment {
    /// Create new enrollment for user
    pub fn new(user_id: Uuid) -> Self {
        Self {
            user_id,
            status: EnrollmentStatus::NotStarted,
            session: None,
            verification_attempts: 0,
            max_attempts: 3,
        }
    }

    /// Start enrollment with session
    pub fn start(&mut self, session: EnrollmentSession) {
        self.status = EnrollmentStatus::InProgress;
        self.session = Some(session);
        self.verification_attempts = 0;
    }

    /// Complete enrollment
    pub fn complete(&mut self) {
        self.status = EnrollmentStatus::Completed;
        self.session = None;
    }

    /// Mark enrollment as failed
    pub fn fail(&mut self, reason: String) {
        self.status = EnrollmentStatus::Failed(reason);
        self.session = None;
    }

    /// Increment verification attempts
    pub fn increment_attempts(&mut self) {
        self.verification_attempts += 1;
    }

    /// Check if max attempts reached
    pub fn is_max_attempts_reached(&self) -> bool {
        self.verification_attempts >= self.max_attempts
    }

    /// Check if session is expired
    pub fn is_expired(&self) -> bool {
        if let Some(session) = &self.session {
            return session.expires_at < Utc::now();
        }
        false
    }
}

/// Enrollment status
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum EnrollmentStatus {
    /// Not yet started
    NotStarted,
    /// Enrollment in progress
    InProgress,
    /// Successfully completed
    Completed,
    /// Failed with reason
    Failed(String),
}

/// Enrollment errors
#[derive(Debug, Error)]
pub enum EnrollmentError {
    #[error("Enrollment session not found")]
    SessionNotFound,

    #[error("Enrollment session expired")]
    SessionExpired,

    #[error("Maximum verification attempts exceeded")]
    MaxAttemptsExceeded,

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

    #[error("Enrollment already completed")]
    AlreadyCompleted,

    #[error("User already has MFA enabled")]
    AlreadyEnabled,

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

    #[error("Encryption error: {0}")]
    EncryptionError(String),
}

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

    #[test]
    fn test_enrollment_lifecycle() {
        let user_id = Uuid::new_v4();
        let mut enrollment = MfaEnrollment::new(user_id);

        // Initial state
        assert_eq!(enrollment.status, EnrollmentStatus::NotStarted);
        assert!(enrollment.session.is_none());

        // Start enrollment
        let session = EnrollmentSession {
            session_id: Uuid::new_v4(),
            user_id,
            qr_code_uri: "otpauth://totp/test".to_string(),
            qr_code_png: vec![],
            manual_entry_key: "TEST123".to_string(),
            expires_at: Utc::now() + chrono::Duration::minutes(15),
        };

        enrollment.start(session);
        assert_eq!(enrollment.status, EnrollmentStatus::InProgress);
        assert!(enrollment.session.is_some());

        // Complete enrollment
        enrollment.complete();
        assert_eq!(enrollment.status, EnrollmentStatus::Completed);
        assert!(enrollment.session.is_none());
    }

    #[test]
    fn test_verification_attempts() {
        let user_id = Uuid::new_v4();
        let mut enrollment = MfaEnrollment::new(user_id);

        assert_eq!(enrollment.verification_attempts, 0);
        assert!(!enrollment.is_max_attempts_reached());

        enrollment.increment_attempts();
        enrollment.increment_attempts();
        enrollment.increment_attempts();

        assert_eq!(enrollment.verification_attempts, 3);
        assert!(enrollment.is_max_attempts_reached());
    }

    #[test]
    fn test_session_expiration() {
        let user_id = Uuid::new_v4();
        let mut enrollment = MfaEnrollment::new(user_id);

        // Session in future (not expired)
        let session = EnrollmentSession {
            session_id: Uuid::new_v4(),
            user_id,
            qr_code_uri: "otpauth://totp/test".to_string(),
            qr_code_png: vec![],
            manual_entry_key: "TEST123".to_string(),
            expires_at: Utc::now() + chrono::Duration::hours(1),
        };

        enrollment.start(session);
        assert!(!enrollment.is_expired());

        // Session in past (expired)
        let expired_session = EnrollmentSession {
            session_id: Uuid::new_v4(),
            user_id,
            qr_code_uri: "otpauth://totp/test".to_string(),
            qr_code_png: vec![],
            manual_entry_key: "TEST123".to_string(),
            expires_at: Utc::now() - chrono::Duration::hours(1),
        };

        enrollment.start(expired_session);
        assert!(enrollment.is_expired());
    }
}
