//! MFA service orchestration layer

use std::sync::Arc;

use webauthn_rs::prelude::Url;

use crate::error::{auth, http, infrastructure, ControlCenterError, Result};
use crate::mfa::storage::{MfaStorage, SqliteMfaStorage};
use crate::mfa::totp::TotpService;
use crate::mfa::types::*;
use crate::mfa::webauthn::WebAuthnService;
use crate::storage::database::Database;

/// MFA service managing all MFA operations
pub struct MfaService {
    /// TOTP service
    totp: TotpService,
    /// WebAuthn service
    webauthn: WebAuthnService,
    /// Storage layer
    storage: Arc<dyn MfaStorage>,
}

impl MfaService {
    /// Create new MFA service
    pub async fn new(
        issuer: String,
        rp_id: String,
        rp_name: String,
        origin: String,
        db: Database,
    ) -> Result<Self> {
        let totp = TotpService::new(issuer);

        let origin_url = Url::parse(&origin).map_err(|e| {
            ControlCenterError::Infrastructure(infrastructure::InfrastructureError::Internal(
                format!("Invalid origin URL: {}", e),
            ))
        })?;

        let webauthn = WebAuthnService::new(rp_id, rp_name, origin_url)?;

        let storage = Arc::new(SqliteMfaStorage::new(db).await?);

        Ok(Self {
            totp,
            webauthn,
            storage,
        })
    }

    // ============================================================================
    // TOTP Operations
    // ============================================================================

    /// Enroll TOTP for user
    pub async fn enroll_totp(&self, user_id: &str, user_email: &str) -> Result<TotpEnrollResponse> {
        // Create TOTP device
        let (device, response) = self.totp.create_enrollment(user_id, user_email)?;

        // Store device (not enabled yet, must verify first)
        self.storage.create_totp_device(&device).await?;

        Ok(response)
    }

    /// Verify TOTP code and enable device
    pub async fn verify_totp(
        &self,
        user_id: &str,
        user_email: &str,
        code: &str,
        device_id: Option<&str>,
    ) -> Result<MfaVerification> {
        let devices = self.storage.get_totp_devices_by_user(user_id).await?;

        if devices.is_empty() {
            return Err(ControlCenterError::Http(http::HttpError::NotFound(
                "No TOTP devices found".to_string(),
            )));
        }

        // Try specific device or all devices
        let devices_to_check: Vec<_> = if let Some(device_id) = device_id {
            devices.into_iter().filter(|d| d.id == device_id).collect()
        } else {
            devices
        };

        for mut device in devices_to_check {
            // Try TOTP code
            if self.totp.verify_code(&mut device, code, user_email)? {
                // Enable device if first verification
                if !device.enabled {
                    device.enabled = true;
                }

                self.storage.update_totp_device(&device).await?;

                return Ok(MfaVerification {
                    verified: true,
                    device_id: Some(device.id),
                    device_type: Some(MfaDeviceType::Totp),
                    backup_code_used: false,
                });
            }

            // Try backup codes
            if self.totp.verify_backup_code(&mut device, code) {
                self.storage.update_totp_device(&device).await?;

                return Ok(MfaVerification {
                    verified: true,
                    device_id: Some(device.id),
                    device_type: Some(MfaDeviceType::Totp),
                    backup_code_used: true,
                });
            }
        }

        Ok(MfaVerification {
            verified: false,
            device_id: None,
            device_type: None,
            backup_code_used: false,
        })
    }

    /// Disable TOTP for user
    pub async fn disable_totp(&self, user_id: &str) -> Result<()> {
        let devices = self.storage.get_totp_devices_by_user(user_id).await?;

        for device in devices {
            self.storage.delete_totp_device(&device.id).await?;
        }

        Ok(())
    }

    /// Get backup codes for user
    pub async fn get_backup_codes(&self, user_id: &str) -> Result<Vec<String>> {
        let devices = self.storage.get_totp_devices_by_user(user_id).await?;

        if devices.is_empty() {
            return Err(ControlCenterError::Http(http::HttpError::NotFound(
                "No TOTP devices found".to_string(),
            )));
        }

        // Return unused backup codes (without revealing the actual codes)
        // This is intentionally limited for security
        let device = &devices[0];
        let remaining = self.totp.backup_codes_remaining(device);

        Ok(vec![format!("{} backup codes remaining", remaining)])
    }

    /// Regenerate backup codes
    pub async fn regenerate_backup_codes(&self, user_id: &str) -> Result<Vec<String>> {
        let devices = self.storage.get_totp_devices_by_user(user_id).await?;

        if devices.is_empty() {
            return Err(ControlCenterError::Http(http::HttpError::NotFound(
                "No TOTP devices found".to_string(),
            )));
        }

        let mut device = devices.into_iter().next().unwrap();
        let new_codes = self.totp.regenerate_backup_codes(&mut device);

        self.storage.update_totp_device(&device).await?;

        Ok(new_codes)
    }

    // ============================================================================
    // WebAuthn Operations
    // ============================================================================

    /// Start WebAuthn registration
    pub async fn start_webauthn_registration(
        &self,
        user_id: &str,
        user_name: &str,
        user_display_name: &str,
    ) -> Result<WebAuthnRegistrationChallenge> {
        // Get existing credentials to exclude
        let existing_devices = self.storage.get_webauthn_devices_by_user(user_id).await?;
        let existing_credentials: Vec<_> = existing_devices
            .iter()
            .map(|d| d.credential_id.clone().into())
            .collect();

        self.webauthn.start_registration(
            user_id,
            user_name,
            user_display_name,
            existing_credentials,
        )
    }

    /// Finish WebAuthn registration
    pub async fn finish_webauthn_registration(
        &self,
        user_id: &str,
        response: &WebAuthnRegistrationResponse,
    ) -> Result<WebAuthnDevice> {
        let mut device = self.webauthn.finish_registration(response)?;

        // Ensure device belongs to this user
        device.user_id = user_id.to_string();

        // Store device
        self.storage.create_webauthn_device(&device).await?;

        Ok(device)
    }

    /// Start WebAuthn authentication
    pub async fn start_webauthn_authentication(
        &self,
        user_id: &str,
    ) -> Result<WebAuthnAuthenticationChallenge> {
        let devices = self.storage.get_webauthn_devices_by_user(user_id).await?;

        if devices.is_empty() {
            return Err(ControlCenterError::Http(http::HttpError::NotFound(
                "No WebAuthn devices found".to_string(),
            )));
        }

        let device_refs: Vec<_> = devices.iter().filter(|d| d.enabled).collect();

        self.webauthn.start_authentication(device_refs)
    }

    /// Verify WebAuthn authentication
    pub async fn verify_webauthn(
        &self,
        user_id: &str,
        response: &WebAuthnAuthenticationResponse,
    ) -> Result<MfaVerification> {
        let mut devices = self.storage.get_webauthn_devices_by_user(user_id).await?;

        let (verified, device_id) = self
            .webauthn
            .finish_authentication(response, &mut devices)?;

        // Update device counter if verification succeeded
        if verified {
            for device in &devices {
                if Some(&device.id) == device_id.as_ref() {
                    self.storage.update_webauthn_device(device).await?;
                    break;
                }
            }
        }

        Ok(MfaVerification {
            verified,
            device_id,
            device_type: Some(MfaDeviceType::WebAuthn),
            backup_code_used: false,
        })
    }

    /// List WebAuthn devices
    pub async fn list_webauthn_devices(&self, user_id: &str) -> Result<Vec<WebAuthnDeviceInfo>> {
        let devices = self.storage.get_webauthn_devices_by_user(user_id).await?;

        Ok(devices
            .into_iter()
            .map(|d| WebAuthnDeviceInfo {
                id: d.id,
                device_name: d.device_name,
                created_at: d.created_at,
                last_used: d.last_used,
                enabled: d.enabled,
                transports: d.transports,
            })
            .collect())
    }

    /// Remove WebAuthn device
    pub async fn remove_webauthn_device(&self, user_id: &str, device_id: &str) -> Result<()> {
        // Verify device belongs to user
        let device = self
            .storage
            .get_webauthn_device(device_id)
            .await?
            .ok_or_else(|| {
                ControlCenterError::Http(http::HttpError::NotFound("Device not found".to_string()))
            })?;

        if device.user_id != user_id {
            return Err(ControlCenterError::Auth(auth::AuthError::Forbidden(
                "Device belongs to another user".to_string(),
            )));
        }

        self.storage.delete_webauthn_device(device_id).await
    }

    // ============================================================================
    // General MFA Operations
    // ============================================================================

    /// Get MFA status for user
    pub async fn get_mfa_status(&self, user_id: &str) -> Result<MfaStatus> {
        let (totp_devices, webauthn_devices) =
            self.storage.get_all_devices_by_user(user_id).await?;

        let totp_device_infos: Vec<TotpDeviceInfo> = totp_devices
            .iter()
            .map(|d| TotpDeviceInfo {
                id: d.id.clone(),
                created_at: d.created_at,
                last_used: d.last_used,
                enabled: d.enabled,
            })
            .collect();

        let webauthn_device_infos: Vec<WebAuthnDeviceInfo> = webauthn_devices
            .iter()
            .map(|d| WebAuthnDeviceInfo {
                id: d.id.clone(),
                device_name: d.device_name.clone(),
                created_at: d.created_at,
                last_used: d.last_used,
                enabled: d.enabled,
                transports: d.transports.clone(),
            })
            .collect();

        let has_backup_codes = totp_devices.iter().any(|d| self.totp.has_backup_codes(d));

        let enabled =
            totp_devices.iter().any(|d| d.enabled) || webauthn_devices.iter().any(|d| d.enabled);

        Ok(MfaStatus {
            enabled,
            totp_devices: totp_device_infos,
            webauthn_devices: webauthn_device_infos,
            has_backup_codes,
        })
    }

    /// Check if user has MFA enabled
    pub async fn user_has_mfa(&self, user_id: &str) -> Result<bool> {
        self.storage.user_has_mfa(user_id).await
    }

    /// Disable all MFA for user
    pub async fn disable_all_mfa(&self, user_id: &str) -> Result<()> {
        self.storage.delete_all_devices_by_user(user_id).await
    }

    /// Verify any MFA method
    pub async fn verify_any_mfa(
        &self,
        user_id: &str,
        user_email: &str,
        code: &str,
    ) -> Result<MfaVerification> {
        // Try TOTP first
        let totp_result = self.verify_totp(user_id, user_email, code, None).await;
        if let Ok(verification) = totp_result {
            if verification.verified {
                return Ok(verification);
            }
        }

        // If TOTP didn't work, return failed verification
        Ok(MfaVerification {
            verified: false,
            device_id: None,
            device_type: None,
            backup_code_used: false,
        })
    }

    /// Cleanup expired WebAuthn sessions (should be called periodically)
    pub fn cleanup_sessions(&self) {
        self.webauthn.cleanup_sessions();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::storage::database::DatabaseConfig;

    async fn create_test_service() -> MfaService {
        let config = DatabaseConfig {
            url: ":memory:".to_string(),
            max_connections: 5,
        };
        let db = Database::new(config).await.unwrap();

        // Create users table
        sqlx::query(
            r#"
            CREATE TABLE users (
                id TEXT PRIMARY KEY,
                email TEXT NOT NULL UNIQUE,
                name TEXT NOT NULL
            )
            "#,
        )
        .execute(db.pool())
        .await
        .unwrap();

        MfaService::new(
            "TestApp".to_string(),
            "example.com".to_string(),
            "Test Service".to_string(),
            "https://example.com".to_string(),
            db,
        )
        .await
        .unwrap()
    }

    #[tokio::test]
    async fn test_totp_enrollment_and_verification() {
        let service = create_test_service().await;
        let user_id = "test_user";
        let user_email = "test@example.com";

        // Create test user in database
        // (This would be done by the user service in production)

        // Enroll TOTP
        let enrollment = service.enroll_totp(user_id, user_email).await.unwrap();
        assert!(!enrollment.backup_codes.is_empty());
        assert!(enrollment.qr_code.starts_with("data:image/png;base64,"));

        // Verify with correct code (we need to generate it)
        // In real tests, you'd use a known secret and generate the code
        // For now, we'll verify the device was created
        let status = service.get_mfa_status(user_id).await.unwrap();
        assert_eq!(status.totp_devices.len(), 1);
    }

    #[tokio::test]
    async fn test_mfa_status() {
        let service = create_test_service().await;
        let user_id = "test_user";

        // Initially no MFA
        let status = service.get_mfa_status(user_id).await.unwrap();
        assert!(!status.enabled);
        assert_eq!(status.totp_devices.len(), 0);
        assert_eq!(status.webauthn_devices.len(), 0);
    }

    #[tokio::test]
    async fn test_disable_all_mfa() {
        let service = create_test_service().await;
        let user_id = "test_user";
        let user_email = "test@example.com";

        // Enroll TOTP
        service.enroll_totp(user_id, user_email).await.unwrap();

        // Verify MFA exists
        let has_mfa = service.user_has_mfa(user_id).await.unwrap();
        // Will be false until enabled, but device exists
        let status = service.get_mfa_status(user_id).await.unwrap();
        assert_eq!(status.totp_devices.len(), 1);

        // Disable all
        service.disable_all_mfa(user_id).await.unwrap();

        // Verify MFA removed
        let status = service.get_mfa_status(user_id).await.unwrap();
        assert_eq!(status.totp_devices.len(), 0);
    }
}
