//! End-to-end authentication tests
//!
//! Tests the full auth flow including:
//! - Bearer token authentication
//! - API key authentication
//! - Rate limiting
//! - Backend access control

use std::time::Duration;

use mcp_gateway::config::{ApiKeyConfig, AuthConfig, CircuitBreakerConfig};
use mcp_gateway::failsafe::CircuitState;
use mcp_gateway::gateway::auth::{AuthenticatedClient, ResolvedAuthConfig};

/// Test that `ResolvedAuthConfig` correctly resolves from `AuthConfig`
#[test]
fn test_auth_config_resolution() {
    let auth_config = AuthConfig {
        enabled: true,
        bearer_token: Some("test-token".to_string()),
        api_keys: vec![ApiKeyConfig {
            key: "api-key-1".to_string(),
            name: "Test Client".to_string(),
            rate_limit: 100,
            backends: vec!["tavily".to_string()],
            allowed_tools: None,
            denied_tools: None,
            admin: false,
        }],
        public_paths: vec!["/health".to_string()],
        client_circuit_breaker: None,
        single_user: false,
    };

    let resolved = ResolvedAuthConfig::from_config(&auth_config);

    assert!(resolved.enabled);
    assert_eq!(resolved.bearer_token, Some("test-token".to_string()));
    assert_eq!(resolved.api_keys.len(), 1);
    assert_eq!(resolved.api_keys[0].name, "Test Client");
    assert!(resolved.is_public_path("/health"));
    assert!(!resolved.is_public_path("/mcp"));
}

/// Test bearer token validation
#[test]
fn test_bearer_token_auth() {
    let auth_config = AuthConfig {
        enabled: true,
        bearer_token: Some("secret-bearer-token".to_string()),
        api_keys: vec![],
        public_paths: vec![],
        client_circuit_breaker: None,
        single_user: false,
    };

    let resolved = ResolvedAuthConfig::from_config(&auth_config);

    // Valid bearer token
    let client = resolved.validate_token("secret-bearer-token");
    assert!(client.is_some());
    let client = client.unwrap();
    assert_eq!(client.name, "bearer");
    assert!(client.can_access_backend("any-backend"));

    // Invalid token
    assert!(resolved.validate_token("wrong-token").is_none());
}

/// Test API key authentication with backend restrictions
#[test]
fn test_api_key_auth_with_restrictions() {
    let auth_config = AuthConfig {
        enabled: true,
        bearer_token: None,
        api_keys: vec![
            ApiKeyConfig {
                key: "restricted-key".to_string(),
                name: "Restricted Client".to_string(),
                rate_limit: 50,
                backends: vec!["tavily".to_string(), "brave".to_string()],
                allowed_tools: None,
                denied_tools: None,
                admin: false,
            },
            ApiKeyConfig {
                key: "unrestricted-key".to_string(),
                name: "Unrestricted Client".to_string(),
                rate_limit: 0,
                backends: vec![], // empty = all access
                allowed_tools: None,
                denied_tools: None,
                admin: false,
            },
        ],
        public_paths: vec![],
        client_circuit_breaker: None,
        single_user: false,
    };

    let resolved = ResolvedAuthConfig::from_config(&auth_config);

    // Restricted client
    let client = resolved.validate_token("restricted-key").unwrap();
    assert_eq!(client.name, "Restricted Client");
    assert_eq!(client.rate_limit, 50);
    assert!(client.can_access_backend("tavily"));
    assert!(client.can_access_backend("brave"));
    assert!(!client.can_access_backend("context7"));

    // Unrestricted client
    let client = resolved.validate_token("unrestricted-key").unwrap();
    assert_eq!(client.name, "Unrestricted Client");
    assert_eq!(client.rate_limit, 0);
    assert!(client.can_access_backend("any-backend"));
}

/// Test rate limiting enforcement
#[test]
fn test_rate_limiting() {
    let auth_config = AuthConfig {
        enabled: true,
        bearer_token: None,
        api_keys: vec![ApiKeyConfig {
            key: "rate-limited-key".to_string(),
            name: "Rate Limited".to_string(),
            rate_limit: 2, // Very low for testing
            backends: vec![],
            allowed_tools: None,
            denied_tools: None,
            admin: false,
        }],
        public_paths: vec![],
        client_circuit_breaker: None,
        single_user: false,
    };

    let resolved = ResolvedAuthConfig::from_config(&auth_config);

    // First requests should succeed (within limit)
    assert!(resolved.check_rate_limit("Rate Limited"));
    assert!(resolved.check_rate_limit("Rate Limited"));

    // Third request should be rate limited
    assert!(!resolved.check_rate_limit("Rate Limited"));

    // Unknown client should not be rate limited
    assert!(resolved.check_rate_limit("Unknown Client"));
}

#[test]
fn test_resolved_client_rate_limit_creates_identity_bucket() {
    let auth_config = AuthConfig {
        enabled: true,
        bearer_token: None,
        api_keys: vec![],
        public_paths: vec![],
        client_circuit_breaker: None,
        single_user: false,
    };

    let resolved = ResolvedAuthConfig::from_config(&auth_config);
    let temporary_client = AuthenticatedClient {
        name: "temporary@example.com".to_string(),
        rate_limit: 2,
        backends: vec!["*".to_string()],
        allowed_tools: None,
        denied_tools: None,
        admin: false,
    };

    assert!(resolved.check_authenticated_client_rate_limit(&temporary_client));
    assert!(resolved.check_authenticated_client_rate_limit(&temporary_client));
    assert!(!resolved.check_authenticated_client_rate_limit(&temporary_client));
}

/// Test public paths bypass authentication
#[test]
fn test_public_paths() {
    let auth_config = AuthConfig {
        enabled: true,
        bearer_token: Some("token".to_string()),
        api_keys: vec![],
        public_paths: vec![
            "/health".to_string(),
            "/metrics".to_string(),
            "/api/public/".to_string(),
        ],
        client_circuit_breaker: None,
        single_user: false,
    };

    let resolved = ResolvedAuthConfig::from_config(&auth_config);

    // Exact matches
    assert!(resolved.is_public_path("/health"));
    assert!(resolved.is_public_path("/metrics"));

    // Prefix matches
    assert!(resolved.is_public_path("/health/deep"));
    assert!(resolved.is_public_path("/api/public/anything"));

    // Non-public paths
    assert!(!resolved.is_public_path("/mcp"));
    assert!(!resolved.is_public_path("/api/private"));
    assert!(!resolved.is_public_path("/"));
}

/// Test auto-generated bearer token
#[test]
fn test_auto_generated_token() {
    let auth_config = AuthConfig {
        enabled: true,
        bearer_token: Some("auto".to_string()),
        api_keys: vec![],
        public_paths: vec![],
        client_circuit_breaker: None,
        single_user: false,
    };

    let resolved = ResolvedAuthConfig::from_config(&auth_config);

    // Auto-generated token should start with "mcp_" and be 47 chars
    let token = resolved.bearer_token.as_ref().unwrap();
    assert!(token.starts_with("mcp_"), "Token should start with mcp_");
    assert!(token.len() > 40, "Token should be reasonably long");

    // Token should be valid
    assert!(resolved.validate_token(token).is_some());
}

/// Test `AuthenticatedClient` backend access patterns
#[test]
fn test_client_backend_access_patterns() {
    // Wildcard access
    let wildcard_client = AuthenticatedClient {
        name: "wildcard".to_string(),
        rate_limit: 0,
        backends: vec!["*".to_string()],
        allowed_tools: None,
        denied_tools: None,
        admin: false,
    };
    assert!(wildcard_client.can_access_backend("anything"));
    assert!(wildcard_client.can_access_backend("tavily"));

    // Empty backends = all access
    let all_access_client = AuthenticatedClient {
        name: "all".to_string(),
        rate_limit: 0,
        backends: vec![],
        allowed_tools: None,
        denied_tools: None,
        admin: false,
    };
    assert!(all_access_client.can_access_backend("anything"));

    // Specific backends only
    let restricted_client = AuthenticatedClient {
        name: "restricted".to_string(),
        rate_limit: 0,
        backends: vec!["backend-a".to_string(), "backend-b".to_string()],
        allowed_tools: None,
        denied_tools: None,
        admin: false,
    };
    assert!(restricted_client.can_access_backend("backend-a"));
    assert!(restricted_client.can_access_backend("backend-b"));
    assert!(!restricted_client.can_access_backend("backend-c"));
}

/// Test disabled auth passes through
#[test]
fn test_disabled_auth() {
    let auth_config = AuthConfig {
        enabled: false,
        bearer_token: Some("ignored".to_string()),
        api_keys: vec![],
        public_paths: vec![],
        client_circuit_breaker: None,
        single_user: false,
    };

    let resolved = ResolvedAuthConfig::from_config(&auth_config);
    assert!(!resolved.enabled);
}

#[test]
fn test_client_circuit_breaker_is_per_client() {
    let auth_config = AuthConfig {
        enabled: true,
        bearer_token: None,
        api_keys: vec![
            ApiKeyConfig {
                key: "client-a-key".to_string(),
                name: "client-a".to_string(),
                rate_limit: 0,
                backends: vec![],
                allowed_tools: None,
                denied_tools: None,
                admin: false,
            },
            ApiKeyConfig {
                key: "client-b-key".to_string(),
                name: "client-b".to_string(),
                rate_limit: 0,
                backends: vec![],
                allowed_tools: None,
                denied_tools: None,
                admin: false,
            },
        ],
        public_paths: vec![],
        client_circuit_breaker: Some(CircuitBreakerConfig {
            enabled: true,
            failure_threshold: 2,
            success_threshold: 1,
            reset_timeout: Duration::from_secs(60),
        }),
        single_user: false,
    };

    let resolved = ResolvedAuthConfig::from_config(&auth_config);

    assert!(resolved.check_client_circuit_breaker("client-a"));
    resolved.record_client_failure("client-a");
    assert_eq!(
        resolved.client_circuit_state("client-a"),
        Some(CircuitState::Closed)
    );
    resolved.record_client_failure("client-a");

    assert_eq!(
        resolved.client_circuit_state("client-a"),
        Some(CircuitState::Open)
    );
    assert!(!resolved.check_client_circuit_breaker("client-a"));
    assert!(resolved.check_client_circuit_breaker("client-b"));
}

#[test]
fn test_client_circuit_breaker_recovers_after_successful_probe() {
    let auth_config = AuthConfig {
        enabled: true,
        bearer_token: None,
        api_keys: vec![ApiKeyConfig {
            key: "client-key".to_string(),
            name: "recovering-client".to_string(),
            rate_limit: 0,
            backends: vec![],
            allowed_tools: None,
            denied_tools: None,
            admin: false,
        }],
        public_paths: vec![],
        client_circuit_breaker: Some(CircuitBreakerConfig {
            enabled: true,
            failure_threshold: 1,
            success_threshold: 1,
            reset_timeout: Duration::from_millis(1),
        }),
        single_user: false,
    };

    let resolved = ResolvedAuthConfig::from_config(&auth_config);

    resolved.record_client_failure("recovering-client");
    assert_eq!(
        resolved.client_circuit_state("recovering-client"),
        Some(CircuitState::Open)
    );
    std::thread::sleep(Duration::from_millis(2));
    assert!(resolved.check_client_circuit_breaker("recovering-client"));
    assert_eq!(
        resolved.client_circuit_state("recovering-client"),
        Some(CircuitState::HalfOpen)
    );

    resolved.record_client_success("recovering-client");

    assert_eq!(
        resolved.client_circuit_state("recovering-client"),
        Some(CircuitState::Closed)
    );
}
