use anyhow::Result;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::database::{DatabaseType, connection::DatabaseConnection};
use shared::auth::{
    AuthError, BackupCodesResponse, Disable2FARequest, GenerateBackupCodesRequest, Setup2FARequest,
    Setup2FAResponse, TwoFactorStatus,
};

/// 2FA service for managing Time-based One-Time Passwords (TOTP)
/// This is a simplified stub implementation that can be extended with full TOTP functionality
#[derive(Clone)]
pub struct TwoFactorService {
    #[allow(dead_code)]
    database: Option<DatabaseConnection>,
    #[allow(dead_code)]
    app_name: String,
    #[allow(dead_code)]
    issuer: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct User2FA {
    id: Uuid,
    user_id: Uuid,
    secret: String,
    is_enabled: bool,
    backup_codes: Option<Vec<String>>,
    created_at: DateTime<Utc>,
    updated_at: DateTime<Utc>,
    last_used: Option<DateTime<Utc>>,
}

impl TwoFactorService {
    pub fn new(database: DatabaseConnection, app_name: String, issuer: String) -> Self {
        Self {
            database: Some(database),
            app_name,
            issuer,
        }
    }

    pub fn from_pool(
        pool: &crate::database::DatabasePool,
        app_name: String,
        issuer: String,
    ) -> Self {
        let connection = DatabaseConnection::from_pool(pool);
        Self::new(connection, app_name, issuer)
    }

    /// Create a TwoFactorService for testing without database connection
    #[cfg(test)]
    pub fn new_for_testing(app_name: String, issuer: String) -> Self {
        Self {
            database: None,
            app_name,
            issuer,
        }
    }

    /// Generate a new TOTP secret for a user (stub implementation)
    pub async fn setup_2fa(
        &self,
        _user_id: Uuid,
        _user_email: &str,
        _request: Setup2FARequest,
    ) -> Result<Setup2FAResponse, AuthError> {
        // TODO: Implement full TOTP secret generation with database storage
        // For now, return a mock response
        Ok(Setup2FAResponse {
            secret: "JBSWY3DPEHPK3PXP".to_string(), // Example Base32 secret
            qr_code_url: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiIGZpbGw9IndoaXRlIi8+PHRleHQgeD0iNTAiIHk9IjEwMCIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjE0IiBmaWxsPSJibGFjayI+UVIgQ29kZSBQbGFjZWhvbGRlcjwvdGV4dD48L3N2Zz4=".to_string(),
            backup_codes: vec![
                "12345678".to_string(),
                "87654321".to_string(),
                "11111111".to_string(),
                "22222222".to_string(),
                "33333333".to_string(),
                "44444444".to_string(),
                "55555555".to_string(),
                "66666666".to_string(),
            ],
        })
    }

    /// Verify 2FA code and enable 2FA if verification succeeds (stub implementation)
    pub async fn verify_2fa_setup(&self, _user_id: Uuid, _code: &str) -> Result<bool, AuthError> {
        // TODO: Implement TOTP verification with database storage
        // For now, always return true for demo purposes
        Ok(true)
    }

    /// Get 2FA status for a user
    pub async fn get_2fa_status(&self, user_id: Uuid) -> Result<TwoFactorStatus, AuthError> {
        if let Some(ref database) = self.database {
            match database.database_type() {
                DatabaseType::PostgreSQL => self.get_2fa_status_postgres(user_id).await,
                DatabaseType::SQLite => self.get_2fa_status_sqlite(user_id).await,
            }
        } else {
            // For testing without database
            Ok(TwoFactorStatus {
                is_enabled: false,
                backup_codes_remaining: 0,
                last_used: None,
            })
        }
    }

    async fn get_2fa_status_postgres(&self, user_id: Uuid) -> Result<TwoFactorStatus, AuthError> {
        if let Some(ref database) = self.database {
            let row = database
                .fetch_optional(
                    "SELECT is_enabled, backup_codes, last_used FROM user_2fa WHERE user_id = $1",
                    &[user_id.into()],
                )
                .await
                .map_err(|_| AuthError::DatabaseError)?;

            if let Some(row) = row {
                let is_enabled = row
                    .get_bool("is_enabled")
                    .map_err(|_| AuthError::DatabaseError)?;
                let backup_codes: Option<Vec<String>> = row
                    .get_optional_string("backup_codes")
                    .map_err(|_| AuthError::DatabaseError)?
                    .and_then(|codes| serde_json::from_str(codes.as_str()).ok());
                let last_used = row
                    .get_optional_datetime("last_used")
                    .map_err(|_| AuthError::DatabaseError)?;

                Ok(TwoFactorStatus {
                    is_enabled,
                    backup_codes_remaining: backup_codes
                        .map(|codes| codes.len() as u32)
                        .unwrap_or(0),
                    last_used,
                })
            } else {
                Ok(TwoFactorStatus {
                    is_enabled: false,
                    backup_codes_remaining: 0,
                    last_used: None,
                })
            }
        } else {
            Err(AuthError::DatabaseError)
        }
    }

    async fn get_2fa_status_sqlite(&self, user_id: Uuid) -> Result<TwoFactorStatus, AuthError> {
        if let Some(ref database) = self.database {
            let row = database
                .fetch_optional(
                    "SELECT is_enabled, backup_codes, last_used FROM user_2fa WHERE user_id = ?",
                    &[user_id.to_string().into()],
                )
                .await
                .map_err(|_| AuthError::DatabaseError)?;

            if let Some(row) = row {
                let is_enabled = row
                    .get_bool("is_enabled")
                    .map_err(|_| AuthError::DatabaseError)?;
                let backup_codes: Option<Vec<String>> = row
                    .get_optional_string("backup_codes")
                    .map_err(|_| AuthError::DatabaseError)?
                    .and_then(|codes| serde_json::from_str(codes.as_str()).ok());
                let last_used = row
                    .get_optional_datetime("last_used")
                    .map_err(|_| AuthError::DatabaseError)?;

                Ok(TwoFactorStatus {
                    is_enabled,
                    backup_codes_remaining: backup_codes
                        .map(|codes| codes.len() as u32)
                        .unwrap_or(0),
                    last_used,
                })
            } else {
                Ok(TwoFactorStatus {
                    is_enabled: false,
                    backup_codes_remaining: 0,
                    last_used: None,
                })
            }
        } else {
            Err(AuthError::DatabaseError)
        }
    }

    /// Disable 2FA for a user
    pub async fn disable_2fa(
        &self,
        user_id: Uuid,
        _request: Disable2FARequest,
        _ip_address: Option<String>,
        _user_agent: Option<String>,
    ) -> Result<(), AuthError> {
        if let Some(ref database) = self.database {
            match database.database_type() {
                DatabaseType::PostgreSQL => self.disable_2fa_postgres(user_id).await,
                DatabaseType::SQLite => self.disable_2fa_sqlite(user_id).await,
            }
        } else {
            // For testing without database
            Ok(())
        }
    }

    async fn disable_2fa_postgres(&self, user_id: Uuid) -> Result<(), AuthError> {
        if let Some(ref database) = self.database {
            database
                .execute(
                    "UPDATE user_2fa SET is_enabled = false, updated_at = NOW() WHERE user_id = $1",
                    &[user_id.into()],
                )
                .await
                .map_err(|_| AuthError::DatabaseError)?;
            Ok(())
        } else {
            Err(AuthError::DatabaseError)
        }
    }

    async fn disable_2fa_sqlite(&self, user_id: Uuid) -> Result<(), AuthError> {
        if let Some(ref database) = self.database {
            database
                .execute(
                    "UPDATE user_2fa SET is_enabled = 0, updated_at = datetime('now') WHERE user_id = ?",
                    &[user_id.to_string().into()],
                )
                .await
                .map_err(|_| AuthError::DatabaseError)?;
            Ok(())
        } else {
            Err(AuthError::DatabaseError)
        }
    }

    /// Generate new backup codes for a user
    pub async fn generate_backup_codes(
        &self,
        user_id: Uuid,
        _request: GenerateBackupCodesRequest,
    ) -> Result<BackupCodesResponse, AuthError> {
        if let Some(ref database) = self.database {
            match database.database_type() {
                DatabaseType::PostgreSQL => self.generate_backup_codes_postgres(user_id).await,
                DatabaseType::SQLite => self.generate_backup_codes_sqlite(user_id).await,
            }
        } else {
            // For testing without database
            Ok(BackupCodesResponse {
                codes: vec![
                    "12345678".to_string(),
                    "87654321".to_string(),
                    "11111111".to_string(),
                    "22222222".to_string(),
                    "33333333".to_string(),
                    "44444444".to_string(),
                    "55555555".to_string(),
                    "66666666".to_string(),
                ],
                generated_at: Utc::now(),
            })
        }
    }

    async fn generate_backup_codes_postgres(
        &self,
        user_id: Uuid,
    ) -> Result<BackupCodesResponse, AuthError> {
        // Generate 8 random backup codes
        let backup_codes: Vec<String> = (0..8)
            .map(|_| {
                use rand::Rng;
                let mut rng = rand::rng();
                format!("{:08}", rng.random_range(10000000..99999999))
            })
            .collect();

        if let Some(ref database) = self.database {
            let codes_json =
                serde_json::to_string(&backup_codes).map_err(|_| AuthError::DatabaseError)?;

            database
                .execute(
                    "UPDATE user_2fa SET backup_codes = $1, updated_at = NOW() WHERE user_id = $2",
                    &[codes_json.into(), user_id.into()],
                )
                .await
                .map_err(|_| AuthError::DatabaseError)?;

            Ok(BackupCodesResponse {
                codes: backup_codes,
                generated_at: Utc::now(),
            })
        } else {
            Err(AuthError::DatabaseError)
        }
    }

    async fn generate_backup_codes_sqlite(
        &self,
        user_id: Uuid,
    ) -> Result<BackupCodesResponse, AuthError> {
        // Generate 8 random backup codes
        let backup_codes: Vec<String> = (0..8)
            .map(|_| {
                use rand::Rng;
                let mut rng = rand::rng();
                format!("{:08}", rng.random_range(10000000..99999999))
            })
            .collect();

        if let Some(ref database) = self.database {
            let codes_json =
                serde_json::to_string(&backup_codes).map_err(|_| AuthError::DatabaseError)?;

            database
                .execute(
                    "UPDATE user_2fa SET backup_codes = ?, updated_at = datetime('now') WHERE user_id = ?",
                    &[codes_json.into(), user_id.to_string().into()],
                )
                .await
                .map_err(|_| AuthError::DatabaseError)?;

            Ok(BackupCodesResponse {
                codes: backup_codes,
                generated_at: Utc::now(),
            })
        } else {
            Err(AuthError::DatabaseError)
        }
    }

    /// Initialize 2FA tables in the database
    pub async fn init_tables(&self) -> Result<(), AuthError> {
        if let Some(ref database) = self.database {
            match database.database_type() {
                DatabaseType::PostgreSQL => self.init_postgres_tables().await,
                DatabaseType::SQLite => self.init_sqlite_tables().await,
            }
        } else {
            Ok(())
        }
    }

    async fn init_postgres_tables(&self) -> Result<(), AuthError> {
        if let Some(ref database) = self.database {
            database
                .execute(
                    r#"
                    CREATE TABLE IF NOT EXISTS user_2fa (
                        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
                        user_id UUID NOT NULL UNIQUE,
                        secret VARCHAR(255) NOT NULL,
                        is_enabled BOOLEAN NOT NULL DEFAULT FALSE,
                        backup_codes TEXT,
                        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
                        updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
                        last_used TIMESTAMP WITH TIME ZONE,
                        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
                    );

                    CREATE INDEX IF NOT EXISTS idx_user_2fa_user_id ON user_2fa(user_id);
                    "#,
                    &[],
                )
                .await
                .map_err(|_| AuthError::DatabaseError)?;
            Ok(())
        } else {
            Err(AuthError::DatabaseError)
        }
    }

    async fn init_sqlite_tables(&self) -> Result<(), AuthError> {
        if let Some(ref database) = self.database {
            database
                .execute(
                    r#"
                    CREATE TABLE IF NOT EXISTS user_2fa (
                        id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
                        user_id TEXT NOT NULL UNIQUE,
                        secret TEXT NOT NULL,
                        is_enabled INTEGER NOT NULL DEFAULT 0,
                        backup_codes TEXT,
                        created_at TEXT DEFAULT (datetime('now')),
                        updated_at TEXT DEFAULT (datetime('now')),
                        last_used TEXT,
                        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
                    );

                    CREATE INDEX IF NOT EXISTS idx_user_2fa_user_id ON user_2fa(user_id);
                    "#,
                    &[],
                )
                .await
                .map_err(|_| AuthError::DatabaseError)?;
            Ok(())
        } else {
            Err(AuthError::DatabaseError)
        }
    }

    /// Check if 2FA is enabled for a user
    pub async fn is_2fa_enabled(&self, user_id: Uuid) -> Result<bool, AuthError> {
        let status = self.get_2fa_status(user_id).await?;
        Ok(status.is_enabled)
    }

    /// Verify 2FA code for login
    pub async fn verify_2fa_for_login(
        &self,
        user_id: Uuid,
        code: &str,
        _ip_address: Option<String>,
        _user_agent: Option<String>,
    ) -> Result<(), AuthError> {
        // TODO: Implement proper TOTP verification
        // For now, accept any 6-digit code as valid
        if code.len() == 6 && code.chars().all(|c| c.is_ascii_digit()) {
            Ok(())
        } else {
            Err(AuthError::Invalid2FACode)
        }
    }

    /// Verify 2FA code and enable 2FA
    pub async fn verify_and_enable_2fa(
        &self,
        user_id: Uuid,
        code: &str,
        _ip_address: Option<String>,
        _user_agent: Option<String>,
    ) -> Result<(), AuthError> {
        // TODO: Implement proper TOTP verification and enable 2FA
        // For now, accept any 6-digit code as valid
        if code.len() == 6 && code.chars().all(|c| c.is_ascii_digit()) {
            // Enable 2FA for the user
            if let Some(ref database) = self.database {
                match database.database_type() {
                    DatabaseType::PostgreSQL => {
                        database
                            .execute(
                                "UPDATE user_2fa SET is_enabled = true, updated_at = NOW() WHERE user_id = $1",
                                &[user_id.into()],
                            )
                            .await
                            .map_err(|_| AuthError::DatabaseError)?;
                    }
                    DatabaseType::SQLite => {
                        database
                            .execute(
                                "UPDATE user_2fa SET is_enabled = 1, updated_at = datetime('now') WHERE user_id = ?",
                                &[user_id.to_string().into()],
                            )
                            .await
                            .map_err(|_| AuthError::DatabaseError)?;
                    }
                }
            }
            Ok(())
        } else {
            Err(AuthError::Invalid2FACode)
        }
    }

    /// Generate new backup codes (alias for generate_backup_codes)
    pub async fn generate_new_backup_codes(
        &self,
        user_id: Uuid,
        request: GenerateBackupCodesRequest,
        _ip_address: Option<String>,
        _user_agent: Option<String>,
    ) -> Result<BackupCodesResponse, AuthError> {
        self.generate_backup_codes(user_id, request).await
    }
}

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

    #[test]
    fn test_two_factor_service_creation() {
        let service =
            TwoFactorService::new_for_testing("Test App".to_string(), "Test Issuer".to_string());
        assert_eq!(service.app_name, "Test App");
        assert_eq!(service.issuer, "Test Issuer");
    }

    #[tokio::test]
    async fn test_2fa_setup_stub() {
        let service =
            TwoFactorService::new_for_testing("Test App".to_string(), "Test Issuer".to_string());

        let user_id = Uuid::new_v4();
        let request = Setup2FARequest {
            password: "password".to_string(),
        };

        let response = service
            .setup_2fa(user_id, "test@example.com", request)
            .await
            .expect("Setup 2FA should succeed in test");
        assert!(!response.secret.is_empty());
        assert!(!response.qr_code_url.is_empty());
        assert_eq!(response.backup_codes.len(), 8);
    }

    #[tokio::test]
    async fn test_get_2fa_status_without_database() {
        let service =
            TwoFactorService::new_for_testing("Test App".to_string(), "Test Issuer".to_string());

        let user_id = Uuid::new_v4();
        let status = service
            .get_2fa_status(user_id)
            .await
            .expect("Get 2FA status should succeed in test");
        assert!(!status.is_enabled);
        assert_eq!(status.backup_codes_remaining, 0);
        assert!(status.last_used.is_none());
    }
}
