//! Integration examples showing how to use encryption features in the auth system
//!
//! This module demonstrates how to integrate the crypto features with the existing
//! authentication system, including encrypted sessions, encrypted config values,
//! and secure user data handling.

use crate::crypto::{
    CryptoService,
    config::{EncryptedConfigBuilder, EncryptedConfigStore},
    session::{EncryptedSessionConfig, EncryptedSessionStore, session_helpers},
};
use axum::{
    Router,
    extract::State,
    http::StatusCode,
    response::Json,
    routing::{get, post},
};
use serde::{Deserialize, Serialize};
use shared::auth::{LoginCredentials, User};
use std::sync::Arc;
use tower_cookies::Cookies;
use tower_sessions::cookie::SameSite;
use tracing::{error, info};

/// App state with crypto services
#[derive(Clone)]
pub struct AppStateWithCrypto {
    pub crypto: Arc<CryptoService>,
    pub session_store: Arc<EncryptedSessionStore>,
    pub config_store: Arc<EncryptedConfigStore>,
    // Add other services as needed
}

impl AppStateWithCrypto {
    /// Create new app state with crypto services
    pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
        // Initialize crypto service
        let crypto = Arc::new(CryptoService::new()?);

        // Initialize encrypted session store
        let session_config = EncryptedSessionConfig {
            cookie_name: "rustelo_session".to_string(),
            session_lifetime: 3600 * 24, // 24 hours
            secure: std::env::var("ENVIRONMENT") == Ok("production".to_string()),
            http_only: true,
            path: "/".to_string(),
            domain: None,
            same_site: SameSite::Lax,
        };
        let session_store = Arc::new(EncryptedSessionStore::new(crypto.clone(), session_config));

        // Initialize encrypted config store
        let config_store = Arc::new(
            EncryptedConfigBuilder::new(crypto.clone())
                .with_file("config/encrypted.json".to_string())
                .with_auto_load_env()
                .build()
                .await?,
        );

        Ok(Self {
            crypto,
            session_store,
            config_store,
        })
    }

    /// Get database URL from encrypted config
    pub fn get_database_url(&self) -> Result<String, Box<dyn std::error::Error>> {
        Ok(crate::crypto::config::utils::get_database_url(
            &self.config_store,
        )?)
    }

    /// Get JWT secret from encrypted config
    pub fn get_jwt_secret(&self) -> Result<String, Box<dyn std::error::Error>> {
        Ok(crate::crypto::config::utils::get_jwt_secret(
            &self.config_store,
        )?)
    }
}

/// Login response with encrypted session
#[derive(Debug, Serialize, Deserialize)]
pub struct EncryptedLoginResponse {
    pub user: User,
    pub session_token: String,
    pub expires_in: i64,
}

/// Login handler with encrypted session
pub async fn login_with_encrypted_session(
    State(app_state): State<AppStateWithCrypto>,
    cookies: Cookies,
    Json(credentials): Json<LoginCredentials>,
) -> Result<Json<EncryptedLoginResponse>, StatusCode> {
    // This would typically involve validating credentials against the database
    // For this example, we'll create a mock user
    let user = create_mock_user(&credentials.email);

    // Create encrypted session
    match session_helpers::login_user(&app_state.session_store, &cookies, &user).await {
        Ok(session_token) => {
            info!("User {} logged in with encrypted session", user.email);

            Ok(Json(EncryptedLoginResponse {
                user,
                session_token,
                expires_in: app_state.session_store.config().session_lifetime,
            }))
        }
        Err(e) => {
            error!("Failed to create encrypted session: {}", e);
            Err(StatusCode::INTERNAL_SERVER_ERROR)
        }
    }
}

/// Logout handler with encrypted session
pub async fn logout_with_encrypted_session(
    State(app_state): State<AppStateWithCrypto>,
    cookies: Cookies,
) -> Result<Json<serde_json::Value>, StatusCode> {
    session_helpers::logout_user(&app_state.session_store, &cookies);
    info!("User logged out, encrypted session cleared");

    Ok(Json(serde_json::json!({
        "message": "Successfully logged out"
    })))
}

/// Get current user from encrypted session
pub async fn get_current_user_from_encrypted_session(
    State(app_state): State<AppStateWithCrypto>,
    cookies: Cookies,
) -> Result<Json<User>, StatusCode> {
    if let Some(encrypted_session) = app_state.session_store.get_session_cookie(&cookies) {
        match app_state.session_store.get_session(&encrypted_session) {
            Ok(session_data) => {
                // Convert session data back to User
                let user = session_data_to_user(&session_data);
                Ok(Json(user))
            }
            Err(e) => {
                error!("Failed to decrypt session: {}", e);
                app_state.session_store.remove_session_cookie(&cookies);
                Err(StatusCode::UNAUTHORIZED)
            }
        }
    } else {
        Err(StatusCode::UNAUTHORIZED)
    }
}

/// Update user profile with encrypted session
pub async fn update_user_profile_encrypted(
    State(app_state): State<AppStateWithCrypto>,
    cookies: Cookies,
    Json(profile_data): Json<serde_json::Value>,
) -> Result<Json<User>, StatusCode> {
    // Get current user from encrypted session
    if let Some(encrypted_session) = app_state.session_store.get_session_cookie(&cookies) {
        match app_state.session_store.get_session(&encrypted_session) {
            Ok(session_data) => {
                // Update user profile (this would typically involve database operations)
                let mut user = session_data_to_user(&session_data);

                // Update profile fields from request
                if let Some(categories) = profile_data.get("categories") {
                    if let Ok(categories_vec) =
                        serde_json::from_value::<Vec<String>>(categories.clone())
                    {
                        user.profile.categories = categories_vec;
                    }
                }

                if let Some(tags) = profile_data.get("tags") {
                    if let Ok(tags_vec) = serde_json::from_value::<Vec<String>>(tags.clone()) {
                        user.profile.tags = tags_vec;
                    }
                }

                if let Some(preferences) = profile_data.get("preferences") {
                    if let Ok(prefs_map) = serde_json::from_value::<
                        std::collections::HashMap<String, String>,
                    >(preferences.clone())
                    {
                        user.profile.preferences = prefs_map;
                    }
                }

                // Create new encrypted session with updated user data
                match session_helpers::refresh_user_session(
                    &app_state.session_store,
                    &cookies,
                    &user,
                )
                .await
                {
                    Ok(_) => {
                        info!("User profile updated with encrypted session refresh");
                        Ok(Json(user))
                    }
                    Err(e) => {
                        error!("Failed to refresh encrypted session: {}", e);
                        Err(StatusCode::INTERNAL_SERVER_ERROR)
                    }
                }
            }
            Err(e) => {
                error!("Failed to decrypt session: {}", e);
                app_state.session_store.remove_session_cookie(&cookies);
                Err(StatusCode::UNAUTHORIZED)
            }
        }
    } else {
        Err(StatusCode::UNAUTHORIZED)
    }
}

/// Admin endpoint to view encrypted config status
pub async fn admin_config_status(
    State(app_state): State<AppStateWithCrypto>,
    cookies: Cookies,
) -> Result<Json<serde_json::Value>, StatusCode> {
    // Check if user is admin (this would use the encrypted session middleware)
    if !is_admin_user(&app_state, &cookies).await {
        return Err(StatusCode::FORBIDDEN);
    }

    let encryption_status = app_state.config_store.get_encryption_status();
    let keys = app_state.config_store.keys();

    Ok(Json(serde_json::json!({
        "total_keys": keys.len(),
        "encrypted_keys": encryption_status.values().filter(|&&v| v).count(),
        "plain_keys": encryption_status.values().filter(|&&v| !v).count(),
        "keys": keys,
        "encryption_status": encryption_status
    })))
}

/// Admin endpoint to encrypt a config value
pub async fn admin_encrypt_config_value(
    State(app_state): State<AppStateWithCrypto>,
    cookies: Cookies,
    Json(request): Json<serde_json::Value>,
) -> Result<Json<serde_json::Value>, StatusCode> {
    // Check if user is admin
    if !is_admin_user(&app_state, &cookies).await {
        return Err(StatusCode::FORBIDDEN);
    }

    let key = request
        .get("key")
        .and_then(|v| v.as_str())
        .ok_or(StatusCode::BAD_REQUEST)?;
    let value = request
        .get("value")
        .and_then(|v| v.as_str())
        .ok_or(StatusCode::BAD_REQUEST)?;
    let hint = request
        .get("hint")
        .and_then(|v| v.as_str())
        .map(|s| s.to_string());

    // This would need to be a mutable reference in a real implementation
    // For now, we'll demonstrate the concept
    match app_state.crypto.encrypt_string(value) {
        Ok(encrypted_value) => {
            info!("Config value '{}' encrypted successfully", key);
            Ok(Json(serde_json::json!({
                "message": "Config value encrypted successfully",
                "key": key,
                "hint": hint,
                "encrypted_length": encrypted_value.len()
            })))
        }
        Err(e) => {
            error!("Failed to encrypt config value '{}': {}", key, e);
            Err(StatusCode::INTERNAL_SERVER_ERROR)
        }
    }
}

/// Create router with crypto-enabled routes
pub fn create_crypto_routes() -> Router<AppStateWithCrypto> {
    Router::new()
        .route("/auth/login", post(login_with_encrypted_session))
        .route("/auth/logout", post(logout_with_encrypted_session))
        .route("/auth/me", get(get_current_user_from_encrypted_session))
        .route("/auth/profile", post(update_user_profile_encrypted))
        .route("/admin/config/status", get(admin_config_status))
        .route("/admin/config/encrypt", post(admin_encrypt_config_value))
}

/// Helper function to create a mock user (replace with actual user lookup)
fn create_mock_user(email: &str) -> User {
    use chrono::Utc;
    use shared::auth::{Role, UserProfile};
    use uuid::Uuid;

    User {
        id: Uuid::new_v4(),
        email: email.to_string(),
        username: email.split('@').next().unwrap_or("user").to_string(),
        display_name: Some(format!(
            "User {}",
            email.split('@').next().unwrap_or("Unknown")
        )),
        avatar_url: None,
        roles: vec![Role::User],
        is_active: true,
        email_verified: true,
        created_at: Utc::now(),
        updated_at: Utc::now(),
        last_login: Some(Utc::now()),
        two_factor_enabled: false,
        profile: UserProfile {
            first_name: None,
            last_name: None,
            bio: None,
            timezone: Some("UTC".to_string()),
            locale: Some("en".to_string()),
            preferences: [("theme".to_string(), "light".to_string())].into(),
            categories: vec!["general".to_string()],
            tags: vec!["user".to_string()],
        },
    }
}

/// Helper function to convert session data back to User
fn session_data_to_user(session_data: &crate::crypto::EncryptedSessionData) -> User {
    use chrono::Utc;
    use shared::auth::{Role, UserProfile};
    use uuid::Uuid;

    let roles: Vec<Role> = session_data
        .roles
        .iter()
        .filter_map(|r| match r.as_str() {
            "Admin" => Some(Role::Admin),
            "Moderator" => Some(Role::Moderator),
            "User" => Some(Role::User),
            "Guest" => Some(Role::Guest),
            _ => None,
        })
        .collect();

    User {
        id: Uuid::parse_str(&session_data.user_id).unwrap_or_default(),
        email: session_data.email.clone(),
        username: session_data.username.clone(),
        display_name: session_data.display_name.clone(),
        avatar_url: None,
        roles,
        is_active: true,
        email_verified: true,
        created_at: Utc::now(),
        updated_at: Utc::now(),
        last_login: Some(Utc::now()),
        two_factor_enabled: false,
        profile: UserProfile {
            first_name: None,
            last_name: None,
            bio: None,
            timezone: None,
            locale: None,
            preferences: session_data.preferences.clone(),
            categories: session_data.categories.clone(),
            tags: session_data.tags.clone(),
        },
    }
}

/// Helper function to check if user is admin
async fn is_admin_user(app_state: &AppStateWithCrypto, cookies: &Cookies) -> bool {
    if let Some(encrypted_session) = app_state.session_store.get_session_cookie(cookies) {
        if let Ok(session_data) = app_state.session_store.get_session(&encrypted_session) {
            return session_data.roles.contains(&"Admin".to_string());
        }
    }
    false
}

/// Example of how to initialize the crypto system in main.rs
pub async fn init_crypto_system() -> Result<AppStateWithCrypto, Box<dyn std::error::Error>> {
    // Load or generate crypto key
    let crypto = Arc::new(CryptoService::new()?);

    // Initialize session store with production-ready config
    let session_config = EncryptedSessionConfig {
        cookie_name: "rustelo_session".to_string(),
        session_lifetime: 3600 * 24, // 24 hours
        secure: std::env::var("ENVIRONMENT").unwrap_or_default() == "production",
        http_only: true,
        path: "/".to_string(),
        domain: std::env::var("COOKIE_DOMAIN").ok(),
        same_site: SameSite::Lax,
    };
    let session_store = Arc::new(EncryptedSessionStore::new(crypto.clone(), session_config));

    // Initialize config store with encrypted sensitive values
    let config_store = Arc::new(
        EncryptedConfigBuilder::new(crypto.clone())
            .with_file("config/encrypted.json".to_string())
            .with_auto_load_env()
            .with_env_mapping("database_url".to_string(), "DATABASE_URL".to_string())
            .with_env_mapping("jwt_secret".to_string(), "JWT_SECRET".to_string())
            .with_env_mapping("session_secret".to_string(), "SESSION_SECRET".to_string())
            .build()
            .await?,
    );

    // Log crypto system initialization
    info!("Crypto system initialized successfully");
    info!(
        "Session store configured with {} second lifetime",
        session_store.config().session_lifetime
    );
    info!(
        "Config store loaded with {} values",
        config_store.keys().len()
    );

    Ok(AppStateWithCrypto {
        crypto,
        session_store,
        config_store,
    })
}

/// Example middleware setup for encrypted sessions
pub fn setup_crypto_middleware(app: Router<AppStateWithCrypto>) -> Router<AppStateWithCrypto> {
    // use crate::crypto::session::encrypted_session_middleware;
    // use axum::middleware;

    // TODO: Implement crypto middleware when ready
    // app.layer(middleware::from_fn_with_state(
    //     app_state,
    //     encrypted_session_middleware,
    // ))
    app
}

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

    #[tokio::test]
    async fn test_app_state_creation() {
        let app_state = AppStateWithCrypto::new().await;
        assert!(app_state.is_ok());
    }

    #[test]
    fn test_mock_user_creation() {
        let user = create_mock_user("test@example.com");
        assert_eq!(user.email, "test@example.com");
        assert_eq!(user.username, "test");
        assert!(user.profile.categories.contains(&"general".to_string()));
    }

    #[test]
    fn test_session_data_conversion() {
        let session_data = crate::crypto::EncryptedSessionData {
            user_id: uuid::Uuid::new_v4().to_string(),
            email: "test@example.com".to_string(),
            username: "testuser".to_string(),
            display_name: Some("Test User".to_string()),
            roles: vec!["User".to_string(), "Admin".to_string()],
            categories: vec!["tech".to_string()],
            tags: vec!["rust".to_string()],
            preferences: HashMap::new(),
            created_at: chrono::Utc::now().timestamp(),
            expires_at: chrono::Utc::now().timestamp() + 3600,
        };

        let user = session_data_to_user(&session_data);
        assert_eq!(user.email, "test@example.com");
        assert_eq!(user.username, "testuser");
        assert_eq!(user.roles.len(), 2);
        assert!(user.profile.categories.contains(&"tech".to_string()));
        assert!(user.profile.tags.contains(&"rust".to_string()));
    }
}
