//! Common MFA types and data structures

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use validator::Validate;

/// MFA device type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MfaDeviceType {
    /// Time-based One-Time Password (TOTP)
    Totp,
    /// WebAuthn/FIDO2 security key
    WebAuthn,
}

/// TOTP algorithm
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "UPPERCASE")]
pub enum TotpAlgorithm {
    #[serde(rename = "SHA1")]
    #[default]
    Sha1,
    #[serde(rename = "SHA256")]
    Sha256,
    #[serde(rename = "SHA512")]
    Sha512,
}

/// TOTP device configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TotpDevice {
    /// Unique device identifier
    pub id: String,
    /// User ID owning this device
    pub user_id: String,
    /// Base32-encoded secret
    #[serde(skip_serializing)]
    pub secret: String,
    /// Hash algorithm
    pub algorithm: TotpAlgorithm,
    /// Number of digits (6 or 8)
    pub digits: u32,
    /// Time step in seconds (typically 30)
    pub period: u32,
    /// When device was created
    pub created_at: DateTime<Utc>,
    /// Last successful verification time
    pub last_used: Option<DateTime<Utc>>,
    /// Backup codes for recovery
    #[serde(skip)]
    pub backup_codes: Vec<BackupCode>,
    /// Whether device is enabled
    pub enabled: bool,
}

impl TotpDevice {
    /// Create new TOTP device with default settings
    pub fn new(user_id: String, secret: String) -> Self {
        Self {
            id: Uuid::new_v4().to_string(),
            user_id,
            secret,
            algorithm: TotpAlgorithm::default(),
            digits: 6,
            period: 30,
            created_at: Utc::now(),
            last_used: None,
            backup_codes: Vec::new(),
            enabled: false, // Must be verified before enabling
        }
    }
}

/// WebAuthn device configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebAuthnDevice {
    /// Unique device identifier
    pub id: String,
    /// User ID owning this device
    pub user_id: String,
    /// WebAuthn credential ID
    #[serde(with = "base64_serde")]
    pub credential_id: Vec<u8>,
    /// Public key for verification
    #[serde(with = "base64_serde")]
    pub public_key: Vec<u8>,
    /// Signature counter (prevents replay attacks)
    pub counter: u32,
    /// User-friendly device name
    pub device_name: String,
    /// When device was created
    pub created_at: DateTime<Utc>,
    /// Last successful verification time
    pub last_used: Option<DateTime<Utc>>,
    /// Whether device is enabled
    pub enabled: bool,
    /// Attestation type
    pub attestation_type: Option<String>,
    /// Transports supported
    pub transports: Vec<String>,
}

impl WebAuthnDevice {
    /// Create new WebAuthn device
    pub fn new(
        user_id: String,
        credential_id: Vec<u8>,
        public_key: Vec<u8>,
        device_name: String,
    ) -> Self {
        Self {
            id: Uuid::new_v4().to_string(),
            user_id,
            credential_id,
            public_key,
            counter: 0,
            device_name,
            created_at: Utc::now(),
            last_used: None,
            enabled: true,
            attestation_type: None,
            transports: Vec::new(),
        }
    }
}

/// Backup code for TOTP recovery
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupCode {
    /// The backup code (10 characters, alphanumeric)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub code: Option<String>,
    /// Hash of the code for storage
    #[serde(skip_serializing)]
    pub code_hash: String,
    /// Whether code has been used
    pub used: bool,
    /// When code was used
    pub used_at: Option<DateTime<Utc>>,
}

impl BackupCode {
    /// Create new backup code
    pub fn new(code: String) -> Self {
        use sha2::{Digest, Sha256};
        let code_hash = format!("{:x}", Sha256::digest(code.as_bytes()));
        Self {
            code: Some(code),
            code_hash,
            used: false,
            used_at: None,
        }
    }

    /// Verify backup code
    pub fn verify(&self, code: &str) -> bool {
        use sha2::{Digest, Sha256};
        if self.used {
            return false;
        }
        let hash = format!("{:x}", Sha256::digest(code.as_bytes()));
        constant_time_eq::constant_time_eq(self.code_hash.as_bytes(), hash.as_bytes())
    }

    /// Mark code as used
    pub fn mark_used(&mut self) {
        self.used = true;
        self.used_at = Some(Utc::now());
        self.code = None; // Remove plaintext code
    }
}

/// MFA enrollment request
#[derive(Debug, Clone, Deserialize, Validate)]
pub struct MfaEnrollRequest {
    /// Type of MFA to enroll
    pub device_type: MfaDeviceType,
    /// Device name (for WebAuthn)
    #[validate(length(min = 1, max = 100))]
    pub device_name: Option<String>,
}

/// TOTP enrollment response
#[derive(Debug, Clone, Serialize)]
pub struct TotpEnrollResponse {
    /// Device ID
    pub device_id: String,
    /// Secret for manual entry (Base32)
    pub secret: String,
    /// QR code as data URL
    pub qr_code: String,
    /// Backup codes (shown once)
    pub backup_codes: Vec<String>,
    /// OTP Auth URL for compatibility
    pub otpauth_url: String,
}

/// TOTP verification request
#[derive(Debug, Clone, Deserialize, Validate)]
pub struct TotpVerifyRequest {
    /// Device ID (optional, if not provided uses any enabled device)
    pub device_id: Option<String>,
    /// 6 or 8 digit code
    #[validate(length(min = 6, max = 8))]
    // TODO: Fix regex validator - AsRegex trait not satisfied
    // #[validate(regex(path = "crate::mfa::types::TOTP_CODE_REGEX"))]
    pub code: String,
}

/// WebAuthn registration challenge
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebAuthnRegistrationChallenge {
    /// Session ID for this registration
    pub session_id: String,
    /// Challenge data for client
    pub challenge_data: serde_json::Value,
}

/// WebAuthn registration response
#[derive(Debug, Clone, Deserialize)]
pub struct WebAuthnRegistrationResponse {
    /// Session ID from challenge
    pub session_id: String,
    /// Device name
    pub device_name: String,
    /// Client credential response
    pub credential: serde_json::Value,
}

/// WebAuthn authentication challenge
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebAuthnAuthenticationChallenge {
    /// Session ID for this authentication
    pub session_id: String,
    /// Challenge data for client
    pub challenge_data: serde_json::Value,
}

/// WebAuthn authentication response
#[derive(Debug, Clone, Deserialize)]
pub struct WebAuthnAuthenticationResponse {
    /// Session ID from challenge
    pub session_id: String,
    /// Client authentication response
    pub credential: serde_json::Value,
}

/// MFA status for a user
#[derive(Debug, Clone, Serialize)]
pub struct MfaStatus {
    /// Whether MFA is enabled
    pub enabled: bool,
    /// TOTP devices
    pub totp_devices: Vec<TotpDeviceInfo>,
    /// WebAuthn devices
    pub webauthn_devices: Vec<WebAuthnDeviceInfo>,
    /// Whether backup codes are available
    pub has_backup_codes: bool,
}

/// TOTP device info (public view)
#[derive(Debug, Clone, Serialize)]
pub struct TotpDeviceInfo {
    /// Device ID
    pub id: String,
    /// When created
    pub created_at: DateTime<Utc>,
    /// Last used
    pub last_used: Option<DateTime<Utc>>,
    /// Enabled status
    pub enabled: bool,
}

/// WebAuthn device info (public view)
#[derive(Debug, Clone, Serialize)]
pub struct WebAuthnDeviceInfo {
    /// Device ID
    pub id: String,
    /// Device name
    pub device_name: String,
    /// When created
    pub created_at: DateTime<Utc>,
    /// Last used
    pub last_used: Option<DateTime<Utc>>,
    /// Enabled status
    pub enabled: bool,
    /// Transports
    pub transports: Vec<String>,
}

/// Backup codes regeneration response
#[derive(Debug, Clone, Serialize)]
pub struct BackupCodesResponse {
    /// New backup codes (shown once)
    pub backup_codes: Vec<String>,
}

/// MFA verification result
#[derive(Debug, Clone)]
pub struct MfaVerification {
    /// Whether verification succeeded
    pub verified: bool,
    /// Device that was used
    pub device_id: Option<String>,
    /// Device type
    pub device_type: Option<MfaDeviceType>,
    /// Whether a backup code was used
    pub backup_code_used: bool,
}

// Regex for TOTP codes
lazy_static::lazy_static! {
    pub static ref TOTP_CODE_REGEX: regex::Regex = regex::Regex::new(r"^\d{6,8}$").unwrap();
}

// Base64 serialization helpers
mod base64_serde {
    use base64::{engine::general_purpose::STANDARD, Engine};
    use serde::{Deserialize, Deserializer, Serializer};

    pub fn serialize<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&STANDARD.encode(bytes))
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        STANDARD
            .decode(s.as_bytes())
            .map_err(serde::de::Error::custom)
    }
}

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

    #[test]
    fn test_backup_code_verification() {
        let code = "ABCD1234EF".to_string();
        let backup = BackupCode::new(code.clone());

        assert!(backup.verify(&code));
        assert!(!backup.verify("WRONG12345"));
    }

    #[test]
    fn test_backup_code_single_use() {
        let code = "ABCD1234EF".to_string();
        let mut backup = BackupCode::new(code.clone());

        assert!(backup.verify(&code));
        backup.mark_used();
        assert!(!backup.verify(&code)); // Cannot reuse
        assert!(backup.used);
        assert!(backup.used_at.is_some());
    }

    #[test]
    fn test_totp_device_creation() {
        let device = TotpDevice::new("user123".to_string(), "SECRET123".to_string());

        assert_eq!(device.user_id, "user123");
        assert_eq!(device.algorithm, TotpAlgorithm::Sha1);
        assert_eq!(device.digits, 6);
        assert_eq!(device.period, 30);
        assert!(!device.enabled);
    }

    #[test]
    fn test_webauthn_device_creation() {
        let device = WebAuthnDevice::new(
            "user123".to_string(),
            vec![1, 2, 3],
            vec![4, 5, 6],
            "YubiKey 5".to_string(),
        );

        assert_eq!(device.user_id, "user123");
        assert_eq!(device.device_name, "YubiKey 5");
        assert_eq!(device.counter, 0);
        assert!(device.enabled);
    }
}
