//! Authentication and session-lifecycle commands.
//!
//! TRACES: UR-042 | IR-009, IR-014, JA-002 | DR-054

use std::sync::Arc;
use tauri::State;

use crate::auth::{AuthManager, AuthResult, ServerInfo, Session, SessionVerifier};

/// Wrapper for AuthManager to manage in Tauri state
pub struct AuthManagerWrapper(pub Arc<AuthManager>);

/// Wrapper for SessionVerifier to manage in Tauri state
pub struct SessionVerifierWrapper(pub Arc<tokio::sync::Mutex<Option<SessionVerifier>>>);

/// Initialize the auth manager (call on app startup)
/// Restores session from storage if available
#[tauri::command]
#[specta::specta]
pub async fn auth_initialize(
    auth_manager: State<'_, AuthManagerWrapper>,
    database: State<'_, crate::commands::DatabaseWrapper>,
    credentials: State<'_, crate::commands::CredentialStoreWrapper>,
) -> Result<Option<Session>, String> {
    // First check if we already have a session in memory
    if let Some(session) = auth_manager.0.get_session().await {
        return Ok(Some(session));
    }

    // Try to restore session from storage
    log::info!("[AuthManager] Restoring session from storage...");

    // Use the existing storage_get_active_session function
    let active_session =
        match crate::commands::storage::storage_get_active_session(database, credentials).await {
            Ok(Some(session)) => session,
            Ok(None) => {
                log::info!("[AuthManager] No active session in storage");
                return Ok(None);
            }
            Err(e) => {
                log::error!("[AuthManager] Failed to get active session: {}", e);
                return Err(e);
            }
        };

    // Create session object from active session with normalized URL
    let normalized_url = crate::auth::AuthManager::normalize_url(&active_session.server_url)?;

    let session = Session {
        user_id: active_session.user_id,
        username: active_session.username,
        server_id: active_session.server_id,
        server_url: normalized_url,
        server_name: active_session.server_name,
        access_token: active_session.access_token,
        verified: false, // Will be verified in background
        needs_reauth: false,
    };

    // Store in AuthManager
    auth_manager.0.set_session(Some(session.clone())).await;

    log::info!(
        "[AuthManager] Session restored for user: {} with normalized URL: {}",
        session.username,
        session.server_url
    );
    Ok(Some(session))
}

/// Connect to a Jellyfin server and get server info
#[tauri::command]
#[specta::specta]
pub async fn auth_connect_to_server(
    server_url: String,
    auth_manager: State<'_, AuthManagerWrapper>,
) -> Result<ServerInfo, String> {
    auth_manager.0.connect_to_server(&server_url).await
}

/// Login with username and password
#[tauri::command]
#[specta::specta]
pub async fn auth_login(
    server_url: String,
    username: String,
    password: String,
    device_id: String,
    auth_manager: State<'_, AuthManagerWrapper>,
) -> Result<AuthResult, String> {
    let result = auth_manager
        .0
        .login(&server_url, &username, &password, &device_id)
        .await?;

    // Create session from auth result with normalized URL
    let normalized_url = crate::auth::AuthManager::normalize_url(&server_url)?;

    let session = Session {
        user_id: result.user.id.clone(),
        username: result.user.name.clone(),
        server_id: result.server_id.clone(),
        server_url: normalized_url,
        server_name: String::new(), // Will be set by frontend
        access_token: result.access_token.clone(),
        verified: true,
        needs_reauth: false,
    };

    auth_manager.0.set_session(Some(session)).await;

    Ok(result)
}

/// Verify current session
#[tauri::command]
#[specta::specta]
pub async fn auth_verify_session(
    server_url: String,
    user_id: String,
    access_token: String,
    device_id: String,
    auth_manager: State<'_, AuthManagerWrapper>,
) -> Result<bool, String> {
    match auth_manager
        .0
        .verify_session(&server_url, &user_id, &access_token, &device_id)
        .await
    {
        Ok(_) => Ok(true),
        Err(e) => {
            log::warn!("[AuthCommands] Session verification failed: {}", e);
            Ok(false)
        }
    }
}

/// Logout (clear session and call Jellyfin logout endpoint)
#[tauri::command]
#[specta::specta]
pub async fn auth_logout(
    server_url: String,
    access_token: String,
    device_id: String,
    auth_manager: State<'_, AuthManagerWrapper>,
    session_verifier: State<'_, SessionVerifierWrapper>,
) -> Result<(), String> {
    // Stop session verification
    let mut verifier_guard = session_verifier.0.lock().await;
    if let Some(verifier) = verifier_guard.take() {
        verifier.stop();
    }
    drop(verifier_guard);

    // Call Jellyfin logout endpoint
    auth_manager
        .0
        .logout(&server_url, &access_token, &device_id)
        .await?;

    // Clear session
    auth_manager.0.set_session(None).await;

    Ok(())
}

/// Get current session
#[tauri::command]
#[specta::specta]
pub async fn auth_get_session(
    auth_manager: State<'_, AuthManagerWrapper>,
) -> Result<Option<Session>, String> {
    Ok(auth_manager.0.get_session().await)
}

/// Set current session (for restoration from storage)
#[tauri::command]
#[specta::specta]
pub async fn auth_set_session(
    session: Option<Session>,
    auth_manager: State<'_, AuthManagerWrapper>,
) -> Result<(), String> {
    // Normalize the server URL if session is provided
    let normalized_session = match session {
        Some(mut s) => {
            s.server_url = crate::auth::AuthManager::normalize_url(&s.server_url)?;
            Some(s)
        }
        None => None,
    };

    auth_manager.0.set_session(normalized_session).await;
    Ok(())
}

/// Start background session verification
#[tauri::command]
#[specta::specta]
pub async fn auth_start_verification(
    device_id: String,
    app_handle: tauri::AppHandle,
    auth_manager: State<'_, AuthManagerWrapper>,
    session_verifier: State<'_, SessionVerifierWrapper>,
) -> Result<(), String> {
    let mut verifier_guard = session_verifier.0.lock().await;

    // Stop existing verifier if any
    if let Some(verifier) = verifier_guard.take() {
        verifier.stop();
    }

    // Get AuthManager Arc
    let manager = auth_manager.0.clone();

    // Create new verifier
    let mut verifier = SessionVerifier::new(manager, device_id);
    verifier.set_app_handle(app_handle);
    verifier.start().await;

    *verifier_guard = Some(verifier);

    Ok(())
}

/// Stop background session verification
#[tauri::command]
#[specta::specta]
pub async fn auth_stop_verification(
    session_verifier: State<'_, SessionVerifierWrapper>,
) -> Result<(), String> {
    let mut verifier_guard = session_verifier.0.lock().await;

    if let Some(verifier) = verifier_guard.take() {
        verifier.stop();
    }

    Ok(())
}

/// Re-authenticate with password (when session expired)
#[tauri::command]
#[specta::specta]
pub async fn auth_reauthenticate(
    password: String,
    device_id: String,
    auth_manager: State<'_, AuthManagerWrapper>,
) -> Result<AuthResult, String> {
    // Get current session to extract server_url and username
    let session = auth_manager
        .0
        .get_session()
        .await
        .ok_or_else(|| "No active session to re-authenticate".to_string())?;

    // Re-login with stored credentials
    let result = auth_manager
        .0
        .login(
            &session.server_url,
            &session.username,
            &password,
            &device_id,
        )
        .await?;

    // Update session with new token
    let updated_session = Session {
        user_id: result.user.id.clone(),
        username: result.user.name.clone(),
        server_id: result.server_id.clone(),
        server_url: session.server_url,
        server_name: session.server_name,
        access_token: result.access_token.clone(),
        verified: true,
        needs_reauth: false,
    };

    auth_manager.0.set_session(Some(updated_session)).await;

    Ok(result)
}

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

    #[test]
    fn test_session_serialization() {
        let session = Session {
            user_id: "user-123".to_string(),
            username: "john_doe".to_string(),
            server_id: "server-456".to_string(),
            server_url: "https://jellyfin.example.com".to_string(),
            server_name: "My Jellyfin".to_string(),
            access_token: "token-789-xyz".to_string(),
            verified: true,
            needs_reauth: false,
        };

        // Should serialize successfully
        let json = serde_json::to_string(&session);
        assert!(json.is_ok());
        let serialized = json.unwrap();
        assert!(serialized.contains("user-123"));
        assert!(serialized.contains("john_doe"));
        assert!(serialized.contains("server-456"));
    }

    #[test]
    fn test_session_deserialization() {
        let json = r#"{
            "userId": "user-123",
            "username": "john_doe",
            "serverId": "server-456",
            "serverUrl": "https://jellyfin.example.com",
            "serverName": "My Jellyfin",
            "accessToken": "token-789",
            "verified": true,
            "needsReauth": false
        }"#;

        let result: Result<Session, _> = serde_json::from_str(json);
        assert!(result.is_ok());

        let session = result.unwrap();
        assert_eq!(session.user_id, "user-123");
        assert_eq!(session.username, "john_doe");
        assert_eq!(session.server_id, "server-456");
        assert!(session.verified);
        assert!(!session.needs_reauth);
    }

    #[test]
    fn test_session_roundtrip() {
        let original = Session {
            user_id: "user-999".to_string(),
            username: "alice".to_string(),
            server_id: "server-111".to_string(),
            server_url: "https://server.local".to_string(),
            server_name: "Home Server".to_string(),
            access_token: "very-long-token-string".to_string(),
            verified: true,
            needs_reauth: false,
        };

        let json = serde_json::to_string(&original).unwrap();
        let deserialized: Session = serde_json::from_str(&json).unwrap();

        assert_eq!(original.user_id, deserialized.user_id);
        assert_eq!(original.username, deserialized.username);
        assert_eq!(original.server_id, deserialized.server_id);
        assert_eq!(original.server_url, deserialized.server_url);
        assert_eq!(original.access_token, deserialized.access_token);
        assert_eq!(original.verified, deserialized.verified);
    }

    #[test]
    fn test_session_clone() {
        let session = Session {
            user_id: "user-clone".to_string(),
            username: "test_user".to_string(),
            server_id: "server-clone".to_string(),
            server_url: "https://clone.example.com".to_string(),
            server_name: "Clone Server".to_string(),
            access_token: "clone-token".to_string(),
            verified: false,
            needs_reauth: true,
        };

        let cloned = session.clone();
        assert_eq!(session.user_id, cloned.user_id);
        assert_eq!(session.username, cloned.username);
        assert_eq!(session.verified, cloned.verified);
        assert_eq!(session.needs_reauth, cloned.needs_reauth);
    }

    #[test]
    fn test_session_unverified() {
        let session = Session {
            user_id: "user-unverified".to_string(),
            username: "newuser".to_string(),
            server_id: "server-new".to_string(),
            server_url: "https://new.example.com".to_string(),
            server_name: "New Server".to_string(),
            access_token: "new-token".to_string(),
            verified: false,
            needs_reauth: true,
        };

        let json = serde_json::to_string(&session).unwrap();
        assert!(json.contains("false")); // verified: false
        assert!(json.contains("true")); // needs_reauth: true

        let deserialized: Session = serde_json::from_str(&json).unwrap();
        assert!(!deserialized.verified);
        assert!(deserialized.needs_reauth);
    }

    #[test]
    fn test_session_debug() {
        let session = Session {
            user_id: "user-debug".to_string(),
            username: "debug_user".to_string(),
            server_id: "server-debug".to_string(),
            server_url: "https://debug.example.com".to_string(),
            server_name: "Debug Server".to_string(),
            access_token: "debug-token".to_string(),
            verified: true,
            needs_reauth: false,
        };

        let debug_str = format!("{:?}", session);
        assert!(debug_str.contains("user-debug"));
        assert!(debug_str.contains("Session"));
    }

    #[test]
    fn test_auth_manager_wrapper_structure() {
        // Verify wrapper type exists and has correct structure
        assert_eq!(std::mem::size_of::<AuthManagerWrapper>() > 0, true);
    }

    #[test]
    fn test_session_verifier_wrapper_structure() {
        // Verify wrapper type exists and has correct structure
        assert_eq!(std::mem::size_of::<SessionVerifierWrapper>() > 0, true);
    }

    #[test]
    fn test_session_with_special_characters() {
        let session = Session {
            user_id: "user-special-éñ".to_string(),
            username: "user@example.com".to_string(),
            server_id: "server/123".to_string(),
            server_url: "https://jellyfin.example.com:8096".to_string(),
            server_name: "My Jellyfin (v10.8.0)".to_string(),
            access_token: "token+with/special=chars".to_string(),
            verified: true,
            needs_reauth: false,
        };

        let json = serde_json::to_string(&session).unwrap();
        let deserialized: Session = serde_json::from_str(&json).unwrap();

        assert_eq!(session.username, deserialized.username);
        assert_eq!(session.server_name, deserialized.server_name);
        assert_eq!(session.access_token, deserialized.access_token);
    }

    #[test]
    fn test_session_field_presence() {
        let session = Session {
            user_id: "u1".to_string(),
            username: "user1".to_string(),
            server_id: "s1".to_string(),
            server_url: "url1".to_string(),
            server_name: "name1".to_string(),
            access_token: "token1".to_string(),
            verified: true,
            needs_reauth: false,
        };

        let json = serde_json::to_string(&session).unwrap();

        // Verify camelCase serialization (serde rename_all = "camelCase")
        assert!(json.contains("userId"));
        assert!(json.contains("username"));
        assert!(json.contains("serverId"));
        assert!(json.contains("serverUrl"));
        assert!(json.contains("serverName"));
        assert!(json.contains("accessToken"));
        assert!(json.contains("verified"));
        assert!(json.contains("needsReauth"));
    }
}
