//! WebAuthn/FIDO2 implementation for hardware security keys

#![allow(invalid_value)] // Allow zeroed() in tests for session count testing

use std::collections::HashMap;
use std::sync::{Arc, RwLock};

use chrono::Utc;
use uuid::Uuid;
use webauthn_rs::prelude::*;

use crate::error::{auth, http, infrastructure, ControlCenterError, Result};
use crate::mfa::types::{
    WebAuthnAuthenticationChallenge, WebAuthnAuthenticationResponse, WebAuthnDevice,
    WebAuthnRegistrationChallenge, WebAuthnRegistrationResponse,
};

/// WebAuthn service for managing FIDO2 security keys
pub struct WebAuthnService {
    /// WebAuthn instance
    webauthn: Webauthn,
    /// Pending registration sessions
    registration_sessions: Arc<RwLock<HashMap<String, PasskeyRegistration>>>,
    /// Pending authentication sessions
    authentication_sessions: Arc<RwLock<HashMap<String, PasskeyAuthentication>>>,
}

impl WebAuthnService {
    /// Create new WebAuthn service
    pub fn new(rp_id: String, rp_name: String, origin: Url) -> Result<Self> {
        let rp_origin = origin;
        let builder = WebauthnBuilder::new(&rp_id, &rp_origin).map_err(|e| {
            ControlCenterError::from(infrastructure::internal_error(&format!(
                "Failed to create WebAuthn builder: {}",
                e
            )))
        })?;

        let webauthn = builder.rp_name(&rp_name).build().map_err(|e| {
            ControlCenterError::from(infrastructure::internal_error(&format!(
                "Failed to build WebAuthn: {}",
                e
            )))
        })?;

        Ok(Self {
            webauthn,
            registration_sessions: Arc::new(RwLock::new(HashMap::new())),
            authentication_sessions: Arc::new(RwLock::new(HashMap::new())),
        })
    }

    /// Start registration (enrollment) flow
    pub fn start_registration(
        &self,
        user_id: &str,
        user_name: &str,
        user_display_name: &str,
        existing_credentials: Vec<CredentialID>,
    ) -> Result<WebAuthnRegistrationChallenge> {
        // Convert user_id to UUID
        let user_unique_id = Uuid::parse_str(user_id).map_err(|e| {
            ControlCenterError::from(http::bad_request(&format!("Invalid user ID: {}", e)))
        })?;

        // Start registration
        let (ccr, passkey_reg) = self
            .webauthn
            .start_passkey_registration(
                user_unique_id,
                user_name,
                user_display_name,
                Some(existing_credentials),
            )
            .map_err(|e| {
                ControlCenterError::from(infrastructure::internal_error(&format!(
                    "Failed to start registration: {}",
                    e
                )))
            })?;

        // Generate session ID and store session
        let session_id = Uuid::new_v4().to_string();
        let mut sessions = self.registration_sessions.write().unwrap();
        sessions.insert(session_id.clone(), passkey_reg);

        // Convert to JSON
        let challenge_data = serde_json::to_value(&ccr).map_err(|e| {
            ControlCenterError::from(infrastructure::internal_error(&format!(
                "Failed to serialize challenge: {}",
                e
            )))
        })?;

        Ok(WebAuthnRegistrationChallenge {
            session_id,
            challenge_data,
        })
    }

    /// Finish registration flow
    pub fn finish_registration(
        &self,
        response: &WebAuthnRegistrationResponse,
    ) -> Result<WebAuthnDevice> {
        // Retrieve session
        let mut sessions = self.registration_sessions.write().unwrap();
        let passkey_reg = sessions.remove(&response.session_id).ok_or_else(|| {
            ControlCenterError::from(http::bad_request("Invalid or expired session"))
        })?;

        // Parse client response
        let reg: RegisterPublicKeyCredential = serde_json::from_value(response.credential.clone())
            .map_err(|e| {
                ControlCenterError::from(http::bad_request(&format!("Invalid credential: {}", e)))
            })?;

        // Finish registration
        let passkey = self
            .webauthn
            .finish_passkey_registration(&reg, &passkey_reg)
            .map_err(|e| {
                ControlCenterError::from(infrastructure::internal_error(&format!(
                    "Registration failed: {}",
                    e
                )))
            })?;

        // Extract credential data
        let credential_id = passkey.cred_id().to_vec();

        // For public key, we need to serialize it
        // webauthn_rs stores the full Passkey which includes the COSEKey
        // We'll serialize the entire passkey for storage
        let public_key = serde_json::to_vec(&passkey).map_err(|e| {
            ControlCenterError::from(infrastructure::internal_error(&format!(
                "Failed to serialize public key: {}",
                e
            )))
        })?;

        // TODO: WebAuthn API changed - passkey.user_handle() and passkey.counter() no
        // longer exist Extract user_id from credential ID (placeholder - need
        // to store user_id with session) For now, use a placeholder user_id -
        // this should be stored in registration session
        let user_id = "placeholder_user_id".to_string(); // TODO: Store user_id in session during start_registration

        // Create device
        let mut device = WebAuthnDevice::new(
            user_id,
            credential_id,
            public_key,
            response.device_name.clone(),
        );

        // Set counter from passkey (placeholder - API changed)
        device.counter = 0; // TODO: Get counter from updated webauthn-rs API

        // Set attestation info if available
        device.attestation_type = Some("None".to_string()); // Default for passkeys

        Ok(device)
    }

    /// Start authentication flow
    pub fn start_authentication(
        &self,
        devices: Vec<&WebAuthnDevice>,
    ) -> Result<WebAuthnAuthenticationChallenge> {
        // Convert devices to Passkeys
        let passkeys: Vec<Passkey> = devices
            .iter()
            .filter_map(|device| serde_json::from_slice(&device.public_key).ok())
            .collect();

        if passkeys.is_empty() {
            return Err(ControlCenterError::from(http::bad_request(
                "No valid devices found for authentication",
            )));
        }

        // Start authentication
        let (rcr, passkey_auth) = self
            .webauthn
            .start_passkey_authentication(&passkeys)
            .map_err(|e| {
                ControlCenterError::from(infrastructure::internal_error(&format!(
                    "Failed to start authentication: {}",
                    e
                )))
            })?;

        // Generate session ID and store session
        let session_id = Uuid::new_v4().to_string();
        let mut sessions = self.authentication_sessions.write().unwrap();
        sessions.insert(session_id.clone(), passkey_auth);

        // Convert to JSON
        let challenge_data = serde_json::to_value(&rcr).map_err(|e| {
            ControlCenterError::from(infrastructure::internal_error(&format!(
                "Failed to serialize challenge: {}",
                e
            )))
        })?;

        Ok(WebAuthnAuthenticationChallenge {
            session_id,
            challenge_data,
        })
    }

    /// Finish authentication flow
    pub fn finish_authentication(
        &self,
        response: &WebAuthnAuthenticationResponse,
        devices: &mut [WebAuthnDevice],
    ) -> Result<(bool, Option<String>)> {
        // Retrieve session
        let mut sessions = self.authentication_sessions.write().unwrap();
        let passkey_auth = sessions.remove(&response.session_id).ok_or_else(|| {
            ControlCenterError::from(http::bad_request("Invalid or expired session"))
        })?;

        // Parse client response
        let auth: PublicKeyCredential = serde_json::from_value(response.credential.clone())
            .map_err(|e| {
                ControlCenterError::from(http::bad_request(&format!("Invalid credential: {}", e)))
            })?;

        // Finish authentication
        let auth_result = self
            .webauthn
            .finish_passkey_authentication(&auth, &passkey_auth)
            .map_err(|e| {
                ControlCenterError::from(infrastructure::internal_error(&format!(
                    "Authentication failed: {}",
                    e
                )))
            })?;

        // Update device counter and last_used
        let credential_id = auth_result.cred_id().to_vec();
        let new_counter = auth_result.counter();

        for device in devices.iter_mut() {
            if device.credential_id == credential_id {
                // Verify counter is incremented (prevents replay attacks)
                if new_counter <= device.counter {
                    return Err(ControlCenterError::from(auth::unauthorized(
                        "Invalid counter value - possible replay attack",
                    )));
                }

                device.counter = new_counter;
                device.last_used = Some(Utc::now());

                return Ok((true, Some(device.id.clone())));
            }
        }

        Ok((false, None))
    }

    /// Clean up expired sessions (should be called periodically)
    pub fn cleanup_sessions(&self) {
        // In production, you'd want to track session creation times
        // and remove sessions older than a certain threshold (e.g., 5 minutes)
        // For now, we'll implement a simple size-based cleanup

        let mut reg_sessions = self.registration_sessions.write().unwrap();
        if reg_sessions.len() > 1000 {
            reg_sessions.clear();
        }

        let mut auth_sessions = self.authentication_sessions.write().unwrap();
        if auth_sessions.len() > 1000 {
            auth_sessions.clear();
        }
    }
}

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

    fn create_test_service() -> WebAuthnService {
        let origin = Url::parse("https://example.com").unwrap();
        WebAuthnService::new(
            "example.com".to_string(),
            "Test Service".to_string(),
            origin,
        )
        .unwrap()
    }

    #[test]
    fn test_service_creation() {
        let service = create_test_service();
        assert_eq!(service.registration_sessions.read().unwrap().len(), 0);
        assert_eq!(service.authentication_sessions.read().unwrap().len(), 0);
    }

    #[test]
    fn test_start_registration() {
        let service = create_test_service();
        let user_id = Uuid::new_v4().to_string();

        let result = service.start_registration(&user_id, "testuser", "Test User", vec![]);

        assert!(result.is_ok());
        let challenge = result.unwrap();
        assert!(!challenge.session_id.is_empty());
        assert!(challenge.challenge_data.is_object());

        // Session should be stored
        assert_eq!(service.registration_sessions.read().unwrap().len(), 1);
    }

    #[test]
    fn test_start_authentication_no_devices() {
        let service = create_test_service();
        let result = service.start_authentication(vec![]);

        assert!(result.is_err());
        match result {
            Err(ControlCenterError::Http(http::HttpError::BadRequest(msg))) => {
                assert!(msg.contains("No valid devices"));
            }
            _ => panic!("Expected BadRequest error"),
        }
    }

    #[test]
    fn test_cleanup_sessions() {
        let service = create_test_service();

        // Add many sessions
        let mut sessions = service.registration_sessions.write().unwrap();
        for i in 0..1500 {
            // We can't create real PasskeyRegistration objects easily in tests,
            // but we can test the size limit
            sessions.insert(format!("session_{}", i), unsafe { std::mem::zeroed() });
        }
        drop(sessions);

        service.cleanup_sessions();

        // Should be cleaned up
        assert_eq!(service.registration_sessions.read().unwrap().len(), 0);
    }
}
