//! Unit tests for the authentication plugin.

use crate::auth::{self, Claims, VerificationResult};
use crate::error::{AuthError, AuthErrorKind};
use crate::helpers::{SessionInfo, UserInfo, VerifyResponse};
use crate::keyring;
use nu_protocol::Span;

// =============================================================================
// Auth Module Tests
// =============================================================================

#[test]
fn test_verification_result_success() {
    let claims = Claims {
        sub: "user-123".to_string(),
        username: "admin".to_string(),
        email: "admin@example.com".to_string(),
        roles: vec!["admin".to_string(), "user".to_string()],
        exp: chrono::Utc::now().timestamp() + 3600,
        iat: chrono::Utc::now().timestamp(),
        nbf: 0,
        jti: "jti-123".to_string(),
        iss: "provisioning".to_string(),
        aud: "cli".to_string(),
    };

    let result = VerificationResult::success(claims);
    assert!(result.valid);
    assert!(result.claims.is_some());
    assert!(result.error.is_none());
    assert!(result.expires_in.is_some());
    assert!(result.expires_in.unwrap() > 0);
}

#[test]
fn test_verification_result_failure() {
    let result = VerificationResult::failure("Token expired");
    assert!(!result.valid);
    assert!(result.claims.is_none());
    assert!(result.error.is_some());
    assert_eq!(result.error.unwrap(), "Token expired");
}

#[test]
fn test_decode_claims_invalid_token_format() {
    let result = auth::decode_claims_unverified("invalid");
    assert!(result.is_err());

    let error = result.unwrap_err();
    assert_eq!(error.kind, AuthErrorKind::InvalidToken);
}

#[test]
fn test_decode_claims_missing_parts() {
    let result = auth::decode_claims_unverified("only.two");
    assert!(result.is_err());

    let error = result.unwrap_err();
    assert_eq!(error.kind, AuthErrorKind::InvalidToken);
    assert!(error.context.contains("3 parts"));
}

#[test]
fn test_decode_claims_invalid_base64() {
    let result = auth::decode_claims_unverified("header.not_valid_base64!@#$.signature");
    assert!(result.is_err());
}

// =============================================================================
// Error Module Tests
// =============================================================================

#[test]
fn test_auth_error_display() {
    let error = AuthError::new(AuthErrorKind::InvalidCredentials, "Wrong password");
    let display = format!("{}", error);
    assert!(display.contains("invalid credentials"));
    assert!(display.contains("Wrong password"));
}

#[test]
fn test_auth_error_with_source() {
    let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
    let error = AuthError::with_source(AuthErrorKind::KeyringError, "keyring failed", io_error);

    let display = format!("{}", error);
    assert!(display.contains("keyring"));
    assert!(display.contains("caused by"));
}

#[test]
fn test_auth_error_convenience_constructors() {
    let error = AuthError::invalid_credentials("bad password");
    assert_eq!(error.kind, AuthErrorKind::InvalidCredentials);

    let error = AuthError::token_expired("expired 5 minutes ago");
    assert_eq!(error.kind, AuthErrorKind::TokenExpired);

    let error = AuthError::network_error("connection refused");
    assert_eq!(error.kind, AuthErrorKind::NetworkError);

    let error = AuthError::server_error("500 Internal Server Error");
    assert_eq!(error.kind, AuthErrorKind::ServerError);

    let error = AuthError::mfa_failed("invalid code");
    assert_eq!(error.kind, AuthErrorKind::MfaFailed);

    let error = AuthError::configuration_error("missing public key");
    assert_eq!(error.kind, AuthErrorKind::ConfigurationError);
}

#[test]
fn test_auth_error_kind_display() {
    assert_eq!(
        AuthErrorKind::InvalidCredentials.to_string(),
        "invalid credentials"
    );
    assert_eq!(AuthErrorKind::TokenExpired.to_string(), "token expired");
    assert_eq!(AuthErrorKind::InvalidToken.to_string(), "invalid token format");
    assert_eq!(AuthErrorKind::KeyringError.to_string(), "keyring operation failed");
    assert_eq!(AuthErrorKind::NetworkError.to_string(), "network error");
    assert_eq!(AuthErrorKind::MfaFailed.to_string(), "MFA verification failed");
}

#[test]
fn test_auth_error_to_labeled_error() {
    let error = AuthError::new(AuthErrorKind::InvalidCredentials, "test error");
    let labeled: nu_protocol::LabeledError = error.into();
    // LabeledError was created successfully
    assert!(format!("{:?}", labeled).contains("test error"));
}

// =============================================================================
// Keyring Module Tests
// =============================================================================

#[test]
fn test_get_current_username() {
    let username = keyring::get_current_username();
    // Should return a non-empty string
    assert!(!username.is_empty());
}

#[test]
fn test_has_tokens_nonexistent_user() {
    // Should return false for a user that doesn't exist
    let result = keyring::has_tokens("__test_nonexistent_user_xyz_123__");
    assert!(!result);
}

#[test]
fn test_remove_tokens_nonexistent_user() {
    // Should not error when removing tokens for non-existent user
    let result = keyring::remove_tokens("__test_nonexistent_user_xyz_123__");
    assert!(result.is_ok());
}

#[test]
fn test_list_stored_users() {
    // Currently returns empty list (platform-specific)
    let users = keyring::list_stored_users();
    // Just verify it doesn't panic
    let _ = users.len();
}

// =============================================================================
// Helpers Module Tests
// =============================================================================

#[test]
fn test_user_info_to_value() {
    use crate::helpers::user_info_to_value;

    let user = UserInfo {
        id: "user-123".to_string(),
        username: "admin".to_string(),
        email: "admin@example.com".to_string(),
        roles: vec!["admin".to_string(), "user".to_string()],
    };

    let value = user_info_to_value(&user, Span::test_data());
    assert!(matches!(value, nu_protocol::Value::Record { .. }));
}

#[test]
fn test_session_info_to_value() {
    use crate::helpers::session_info_to_value;

    let session = SessionInfo {
        id: "sess-123".to_string(),
        user_id: "user-123".to_string(),
        username: "admin".to_string(),
        roles: vec!["admin".to_string()],
        created_at: "2024-01-01T00:00:00Z".to_string(),
        expires_at: "2024-01-02T00:00:00Z".to_string(),
        is_active: true,
        ip_address: "127.0.0.1".to_string(),
        user_agent: "Mozilla/5.0".to_string(),
    };

    let value = session_info_to_value(&session, Span::test_data());
    assert!(matches!(value, nu_protocol::Value::Record { .. }));
}

#[test]
fn test_verify_response_to_value_valid() {
    use crate::helpers::verify_response_to_value;

    let response = VerifyResponse {
        valid: true,
        user_id: Some("user-123".to_string()),
        username: Some("admin".to_string()),
        roles: Some(vec!["admin".to_string()]),
        expires_at: Some("2024-12-31T23:59:59Z".to_string()),
    };

    let value = verify_response_to_value(&response, Span::test_data());
    assert!(matches!(value, nu_protocol::Value::Record { .. }));
}

#[test]
fn test_verify_response_to_value_invalid() {
    use crate::helpers::verify_response_to_value;

    let response = VerifyResponse {
        valid: false,
        user_id: None,
        username: None,
        roles: None,
        expires_at: None,
    };

    let value = verify_response_to_value(&response, Span::test_data());
    assert!(matches!(value, nu_protocol::Value::Record { .. }));
}

// =============================================================================
// Data Structure Tests
// =============================================================================

#[test]
fn test_claims_serialization() {
    let claims = Claims {
        sub: "user-123".to_string(),
        username: "admin".to_string(),
        email: "admin@example.com".to_string(),
        roles: vec!["admin".to_string()],
        exp: 1700000000,
        iat: 1699996400,
        nbf: 0,
        jti: "jti-123".to_string(),
        iss: "provisioning".to_string(),
        aud: "cli".to_string(),
    };

    // Test that claims can be serialized
    let json = serde_json::to_string(&claims).expect("Should serialize");
    assert!(json.contains("user-123"));
    assert!(json.contains("admin"));

    // Test that claims can be deserialized
    let deserialized: Claims = serde_json::from_str(&json).expect("Should deserialize");
    assert_eq!(deserialized.sub, "user-123");
    assert_eq!(deserialized.username, "admin");
}

#[test]
fn test_user_info_serialization() {
    let user = UserInfo {
        id: "user-123".to_string(),
        username: "admin".to_string(),
        email: "admin@example.com".to_string(),
        roles: vec!["admin".to_string(), "user".to_string()],
    };

    let json = serde_json::to_string(&user).expect("Should serialize");
    assert!(json.contains("user-123"));
    assert!(json.contains("admin@example.com"));

    let deserialized: UserInfo = serde_json::from_str(&json).expect("Should deserialize");
    assert_eq!(deserialized.id, "user-123");
    assert_eq!(deserialized.roles.len(), 2);
}

#[test]
fn test_session_info_serialization() {
    let session = SessionInfo {
        id: "sess-123".to_string(),
        user_id: "user-123".to_string(),
        username: "admin".to_string(),
        roles: vec!["admin".to_string()],
        created_at: "2024-01-01T00:00:00Z".to_string(),
        expires_at: "2024-01-02T00:00:00Z".to_string(),
        is_active: true,
        ip_address: "".to_string(),
        user_agent: "".to_string(),
    };

    let json = serde_json::to_string(&session).expect("Should serialize");
    let deserialized: SessionInfo = serde_json::from_str(&json).expect("Should deserialize");
    assert_eq!(deserialized.id, "sess-123");
    assert!(deserialized.is_active);
}

#[test]
fn test_verify_response_serialization() {
    let response = VerifyResponse {
        valid: true,
        user_id: Some("user-123".to_string()),
        username: Some("admin".to_string()),
        roles: Some(vec!["admin".to_string()]),
        expires_at: Some("2024-12-31T23:59:59Z".to_string()),
    };

    let json = serde_json::to_string(&response).expect("Should serialize");
    let deserialized: VerifyResponse = serde_json::from_str(&json).expect("Should deserialize");
    assert!(deserialized.valid);
    assert_eq!(deserialized.user_id, Some("user-123".to_string()));
}

// =============================================================================
// Plugin Structure Tests
// =============================================================================

#[test]
fn test_plugin_version() {
    use crate::AuthPlugin;
    use nu_plugin::Plugin;

    let plugin = AuthPlugin;
    let version = plugin.version();
    // Version should be non-empty and match Cargo.toml
    assert!(!version.is_empty());
}

#[test]
fn test_plugin_commands() {
    use crate::AuthPlugin;
    use nu_plugin::Plugin;

    let plugin = AuthPlugin;
    let commands = plugin.commands();

    // Should have 6 commands
    assert_eq!(commands.len(), 6);
}

#[test]
fn test_login_command_signature() {
    use crate::Login;
    use nu_plugin::SimplePluginCommand;

    let login = Login;
    assert_eq!(SimplePluginCommand::name(&login), "auth login");

    let sig = SimplePluginCommand::signature(&login);
    // Check required positional argument
    assert!(!sig.required_positional.is_empty());
    // Check examples exist
    assert!(!SimplePluginCommand::examples(&login).is_empty());
}

#[test]
fn test_logout_command_signature() {
    use crate::Logout;
    use nu_plugin::SimplePluginCommand;

    let logout = Logout;
    assert_eq!(SimplePluginCommand::name(&logout), "auth logout");
    assert!(!SimplePluginCommand::examples(&logout).is_empty());
}

#[test]
fn test_verify_command_signature() {
    use crate::Verify;
    use nu_plugin::SimplePluginCommand;

    let verify = Verify;
    assert_eq!(SimplePluginCommand::name(&verify), "auth verify");
    assert!(!SimplePluginCommand::examples(&verify).is_empty());
}

#[test]
fn test_sessions_command_signature() {
    use crate::Sessions;
    use nu_plugin::SimplePluginCommand;

    let sessions = Sessions;
    assert_eq!(SimplePluginCommand::name(&sessions), "auth sessions");
    assert!(!SimplePluginCommand::examples(&sessions).is_empty());
}

#[test]
fn test_mfa_enroll_command_signature() {
    use crate::MfaEnroll;
    use nu_plugin::SimplePluginCommand;

    let mfa_enroll = MfaEnroll;
    assert_eq!(SimplePluginCommand::name(&mfa_enroll), "auth mfa enroll");
    assert!(!SimplePluginCommand::examples(&mfa_enroll).is_empty());
}

#[test]
fn test_mfa_verify_command_signature() {
    use crate::MfaVerify;
    use nu_plugin::SimplePluginCommand;

    let mfa_verify = MfaVerify;
    assert_eq!(SimplePluginCommand::name(&mfa_verify), "auth mfa verify");
    assert!(!SimplePluginCommand::examples(&mfa_verify).is_empty());
}

// =============================================================================
// Constants Tests
// =============================================================================

#[test]
fn test_default_control_center_url() {
    assert_eq!(auth::DEFAULT_CONTROL_CENTER_URL, "http://localhost:8081");
}
