//! Secure token storage using OS keyring.
//!
//! This module provides functions for storing and retrieving authentication
//! tokens using the operating system's native credential storage (Keychain on
//! macOS, Secret Service on Linux, Credential Manager on Windows).

use keyring::Entry;

use crate::error::{AuthError, AuthErrorKind};

/// Service name used for keyring entries.
const SERVICE_NAME_ACCESS: &str = "provisioning-access";
const SERVICE_NAME_REFRESH: &str = "provisioning-refresh";
const SERVICE_NAME_PUBLIC_KEY: &str = "provisioning-public-key";

/// Stored token data structure.
#[derive(Debug, Clone)]
pub struct StoredTokens {
    /// JWT access token
    pub access_token: String,
    /// JWT refresh token
    pub refresh_token: String,
}

/// Stores authentication tokens in the system keyring.
///
/// # Arguments
///
/// * `username` - The username associated with the tokens
/// * `access_token` - The JWT access token
/// * `refresh_token` - The JWT refresh token
///
/// # Returns
///
/// Returns `Ok(())` if tokens were stored successfully, or an `AuthError` if
/// the keyring operation failed.
///
/// # Example
///
/// ```no_run
/// use nu_plugin_auth::keyring::store_tokens;
///
/// store_tokens("admin", "eyJ...", "eyJ...").expect("Failed to store tokens");
/// ```
pub fn store_tokens(
    username: &str,
    access_token: &str,
    refresh_token: &str,
) -> Result<(), AuthError> {
    let entry_access = Entry::new(SERVICE_NAME_ACCESS, username)
        .map_err(|e| AuthError::keyring_error(format!("Failed to create access entry: {}", e)))?;

    let entry_refresh = Entry::new(SERVICE_NAME_REFRESH, username)
        .map_err(|e| AuthError::keyring_error(format!("Failed to create refresh entry: {}", e)))?;

    entry_access
        .set_password(access_token)
        .map_err(|e| AuthError::keyring_error(format!("Failed to store access token: {}", e)))?;

    entry_refresh
        .set_password(refresh_token)
        .map_err(|e| AuthError::keyring_error(format!("Failed to store refresh token: {}", e)))?;

    Ok(())
}

/// Retrieves the access token from the system keyring.
///
/// # Arguments
///
/// * `username` - The username associated with the token
///
/// # Returns
///
/// Returns the access token string if found, or an `AuthError` if not found
/// or the keyring operation failed.
pub fn get_access_token(username: &str) -> Result<String, AuthError> {
    let entry = Entry::new(SERVICE_NAME_ACCESS, username)
        .map_err(|e| AuthError::keyring_error(format!("Failed to access keyring: {}", e)))?;

    entry
        .get_password()
        .map_err(|e| AuthError::new(
            AuthErrorKind::SessionNotFound,
            format!("No access token found for user '{}': {}", username, e),
        ))
}

/// Retrieves the refresh token from the system keyring.
///
/// # Arguments
///
/// * `username` - The username associated with the token
///
/// # Returns
///
/// Returns the refresh token string if found, or an `AuthError` if not found
/// or the keyring operation failed.
pub fn get_refresh_token(username: &str) -> Result<String, AuthError> {
    let entry = Entry::new(SERVICE_NAME_REFRESH, username)
        .map_err(|e| AuthError::keyring_error(format!("Failed to access keyring: {}", e)))?;

    entry
        .get_password()
        .map_err(|e| AuthError::new(
            AuthErrorKind::SessionNotFound,
            format!("No refresh token found for user '{}': {}", username, e),
        ))
}

/// Retrieves both access and refresh tokens from the system keyring.
///
/// # Arguments
///
/// * `username` - The username associated with the tokens
///
/// # Returns
///
/// Returns `StoredTokens` containing both tokens if found.
pub fn get_tokens(username: &str) -> Result<StoredTokens, AuthError> {
    let access_token = get_access_token(username)?;
    let refresh_token = get_refresh_token(username)?;

    Ok(StoredTokens {
        access_token,
        refresh_token,
    })
}

/// Removes authentication tokens from the system keyring.
///
/// # Arguments
///
/// * `username` - The username associated with the tokens
///
/// # Returns
///
/// Returns `Ok(())` if tokens were removed successfully. Does not return an
/// error if tokens were not found.
pub fn remove_tokens(username: &str) -> Result<(), AuthError> {
    // Try to remove access token
    if let Ok(entry) = Entry::new(SERVICE_NAME_ACCESS, username) {
        let _ = entry.delete_credential();
    }

    // Try to remove refresh token
    if let Ok(entry) = Entry::new(SERVICE_NAME_REFRESH, username) {
        let _ = entry.delete_credential();
    }

    Ok(())
}

/// Stores the public key for token verification.
///
/// # Arguments
///
/// * `key_id` - An identifier for the key (e.g., "default" or server URL)
/// * `public_key_pem` - The RSA public key in PEM format
pub fn store_public_key(key_id: &str, public_key_pem: &str) -> Result<(), AuthError> {
    let entry = Entry::new(SERVICE_NAME_PUBLIC_KEY, key_id)
        .map_err(|e| AuthError::keyring_error(format!("Failed to create key entry: {}", e)))?;

    entry
        .set_password(public_key_pem)
        .map_err(|e| AuthError::keyring_error(format!("Failed to store public key: {}", e)))
}

/// Retrieves the public key for token verification.
///
/// # Arguments
///
/// * `key_id` - The identifier for the key
pub fn get_public_key(key_id: &str) -> Result<String, AuthError> {
    let entry = Entry::new(SERVICE_NAME_PUBLIC_KEY, key_id)
        .map_err(|e| AuthError::keyring_error(format!("Failed to access keyring: {}", e)))?;

    entry
        .get_password()
        .map_err(|e| AuthError::configuration_error(format!("Public key '{}' not found: {}", key_id, e)))
}

/// Checks if tokens exist for a user.
///
/// # Arguments
///
/// * `username` - The username to check
///
/// # Returns
///
/// Returns `true` if the access token exists for the user.
pub fn has_tokens(username: &str) -> bool {
    get_access_token(username).is_ok()
}

/// Lists all stored usernames (requires platform-specific implementation).
///
/// Note: This is a best-effort function that may not work on all platforms.
/// The keyring crate does not provide a native list function.
pub fn list_stored_users() -> Vec<String> {
    // The keyring crate doesn't support listing entries
    // This would require platform-specific implementation
    // For now, we return an empty list
    // In production, you might maintain a separate list of users
    vec![]
}

/// Gets the current username from environment or system.
pub fn get_current_username() -> String {
    std::env::var("USER")
        .or_else(|_| std::env::var("USERNAME"))
        .unwrap_or_else(|_| "default".to_string())
}

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

    // Note: These tests interact with the real keyring
    // They should be run with caution and cleaned up after

    const TEST_USER: &str = "nu_plugin_auth_test_user";

    #[test]
    fn test_get_current_username() {
        let username = get_current_username();
        assert!(!username.is_empty());
    }

    #[test]
    fn test_has_tokens_nonexistent() {
        // Should return false for non-existent user
        let result = has_tokens("nonexistent_test_user_12345");
        assert!(!result);
    }

    #[test]
    fn test_remove_tokens_nonexistent() {
        // Should not error when removing non-existent tokens
        let result = remove_tokens("nonexistent_test_user_12345");
        assert!(result.is_ok());
    }

    #[test]
    fn test_list_stored_users() {
        // Currently returns empty list
        let users = list_stored_users();
        assert!(users.is_empty());
    }

    // Integration test - uncomment to test with real keyring
    // #[test]
    // fn test_store_and_retrieve_tokens() {
    //     let access = "test_access_token";
    //     let refresh = "test_refresh_token";
    //
    //     // Store
    //     store_tokens(TEST_USER, access, refresh).expect("Failed to store");
    //
    //     // Retrieve
    //     let stored = get_tokens(TEST_USER).expect("Failed to get");
    //     assert_eq!(stored.access_token, access);
    //     assert_eq!(stored.refresh_token, refresh);
    //
    //     // Clean up
    //     remove_tokens(TEST_USER).expect("Failed to remove");
    //     assert!(!has_tokens(TEST_USER));
    // }
}
