//! Per-client tool scope tests
//!
//! Tests the per-client tool access control functionality including:
//! - Allowlist enforcement (only specified tools accessible)
//! - Denylist enforcement (specified tools blocked)
//! - Glob pattern matching
//! - Interaction with global tool policy

use mcp_gateway::config::ApiKeyConfig;
use mcp_gateway::gateway::auth::AuthenticatedClient;

/// Test that no restrictions allows all tools (fallback to global policy)
#[test]
fn test_no_tool_restrictions() {
    let client = AuthenticatedClient {
        name: "unrestricted".to_string(),
        rate_limit: 0,
        backends: vec![],
        allowed_tools: None,
        denied_tools: None,
        admin: false,
    };

    // All tools should be allowed (fallback to global policy)
    assert!(client.check_tool_scope("server", "search_web").is_ok());
    assert!(client.check_tool_scope("server", "write_file").is_ok());
    assert!(client.check_tool_scope("server", "any_tool").is_ok());
}

/// Test allowlist with exact tool names
#[test]
fn test_allowlist_exact_match() {
    let client = AuthenticatedClient {
        name: "frontend".to_string(),
        rate_limit: 0,
        backends: vec![],
        allowed_tools: Some(vec![
            "search_web".to_string(),
            "read_file".to_string(),
            "list_directory".to_string(),
        ]),
        denied_tools: None,
        admin: false,
    };

    // Tools in allowlist should be permitted
    assert!(client.check_tool_scope("tavily", "search_web").is_ok());
    assert!(client.check_tool_scope("filesystem", "read_file").is_ok());
    assert!(
        client
            .check_tool_scope("filesystem", "list_directory")
            .is_ok()
    );

    // Tools NOT in allowlist should be denied
    let result = client.check_tool_scope("filesystem", "write_file");
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(err.contains("write_file"));
    assert!(err.contains("allowlist"));
    assert!(err.contains("frontend"));

    let result = client.check_tool_scope("server", "execute_command");
    assert!(result.is_err());
}

/// Test allowlist with glob patterns
#[test]
fn test_allowlist_glob_patterns() {
    let client = AuthenticatedClient {
        name: "search_only".to_string(),
        rate_limit: 0,
        backends: vec![],
        allowed_tools: Some(vec!["search_*".to_string(), "read_*".to_string()]),
        denied_tools: None,
        admin: false,
    };

    // Tools matching glob patterns should be allowed
    assert!(client.check_tool_scope("tavily", "search_web").is_ok());
    assert!(client.check_tool_scope("brave", "search_local").is_ok());
    assert!(client.check_tool_scope("filesystem", "read_file").is_ok());
    assert!(client.check_tool_scope("database", "read_query").is_ok());

    // Tools NOT matching patterns should be denied
    assert!(client.check_tool_scope("filesystem", "write_file").is_err());
    assert!(
        client
            .check_tool_scope("server", "execute_command")
            .is_err()
    );
    assert!(client.check_tool_scope("database", "write_query").is_err());
}

/// Test denylist with exact tool names
#[test]
fn test_denylist_exact_match() {
    let client = AuthenticatedClient {
        name: "no_writes".to_string(),
        rate_limit: 0,
        backends: vec![],
        allowed_tools: None,
        denied_tools: Some(vec![
            "write_file".to_string(),
            "delete_file".to_string(),
            "execute_command".to_string(),
        ]),
        admin: false,
    };

    // Tools in denylist should be blocked
    let result = client.check_tool_scope("filesystem", "write_file");
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(err.contains("write_file"));
    assert!(err.contains("blocked"));
    assert!(err.contains("no_writes"));

    assert!(
        client
            .check_tool_scope("filesystem", "delete_file")
            .is_err()
    );
    assert!(
        client
            .check_tool_scope("server", "execute_command")
            .is_err()
    );

    // Tools NOT in denylist should be allowed
    assert!(client.check_tool_scope("filesystem", "read_file").is_ok());
    assert!(client.check_tool_scope("tavily", "search_web").is_ok());
}

/// Test denylist with glob patterns
#[test]
fn test_denylist_glob_patterns() {
    let client = AuthenticatedClient {
        name: "no_filesystem".to_string(),
        rate_limit: 0,
        backends: vec![],
        allowed_tools: None,
        denied_tools: Some(vec!["filesystem_*".to_string(), "exec_*".to_string()]),
        admin: false,
    };

    // Tools matching deny patterns should be blocked
    assert!(
        client
            .check_tool_scope("server", "filesystem_read")
            .is_err()
    );
    assert!(
        client
            .check_tool_scope("server", "filesystem_write")
            .is_err()
    );
    assert!(client.check_tool_scope("server", "exec_command").is_err());
    assert!(client.check_tool_scope("server", "exec_shell").is_err());

    // Tools NOT matching deny patterns should be allowed
    assert!(client.check_tool_scope("tavily", "search_web").is_ok());
    assert!(client.check_tool_scope("database", "query").is_ok());
}

/// Test qualified name matching (server:tool)
#[test]
fn test_qualified_name_matching() {
    let client = AuthenticatedClient {
        name: "specific_server".to_string(),
        rate_limit: 0,
        backends: vec![],
        allowed_tools: Some(vec![
            "filesystem:read_file".to_string(),
            "database:*".to_string(),
        ]),
        denied_tools: None,
        admin: false,
    };

    // Qualified match: filesystem:read_file allowed, but not on other servers
    assert!(client.check_tool_scope("filesystem", "read_file").is_ok());
    assert!(
        client
            .check_tool_scope("other_server", "read_file")
            .is_err()
    );

    // Qualified glob: database:* allows all tools on database server
    assert!(client.check_tool_scope("database", "query").is_ok());
    assert!(client.check_tool_scope("database", "insert").is_ok());
    assert!(client.check_tool_scope("database", "delete").is_ok());

    // But not on other servers
    assert!(client.check_tool_scope("filesystem", "query").is_err());
}

/// Test combination of allowlist and denylist
#[test]
fn test_allowlist_and_denylist_combination() {
    let client = AuthenticatedClient {
        name: "complex".to_string(),
        rate_limit: 0,
        backends: vec![],
        allowed_tools: Some(vec!["filesystem_*".to_string(), "search_*".to_string()]),
        denied_tools: Some(vec![
            "filesystem_write".to_string(),
            "filesystem_delete".to_string(),
        ]),
        admin: false,
    };

    // In allowlist AND NOT in denylist: allowed
    assert!(client.check_tool_scope("server", "filesystem_read").is_ok());
    assert!(client.check_tool_scope("server", "search_web").is_ok());

    // In allowlist BUT in denylist: denylist takes precedence
    assert!(
        client
            .check_tool_scope("server", "filesystem_write")
            .is_err()
    );
    assert!(
        client
            .check_tool_scope("server", "filesystem_delete")
            .is_err()
    );

    // NOT in allowlist: denied (even if not in denylist)
    assert!(
        client
            .check_tool_scope("server", "execute_command")
            .is_err()
    );
}

/// Test `ApiKeyConfig` with tool scopes (config layer)
#[test]
fn test_api_key_config_with_tool_scopes() {
    let config = ApiKeyConfig {
        key: "test-key".to_string(),
        name: "Frontend App".to_string(),
        rate_limit: 0,
        backends: vec![],
        allowed_tools: Some(vec!["search_*".to_string(), "read_*".to_string()]),
        denied_tools: Some(vec!["read_secrets".to_string()]),
        admin: false,
    };

    // Verify config fields are set correctly
    assert_eq!(config.name, "Frontend App");
    assert!(config.allowed_tools.is_some());
    assert_eq!(config.allowed_tools.as_ref().unwrap().len(), 2);
    assert!(config.denied_tools.is_some());
    assert_eq!(config.denied_tools.as_ref().unwrap().len(), 1);
}

/// Test empty allowlist (deny all)
#[test]
fn test_empty_allowlist() {
    let client = AuthenticatedClient {
        name: "deny_all".to_string(),
        rate_limit: 0,
        backends: vec![],
        allowed_tools: Some(vec![]), // Empty allowlist = nothing allowed
        denied_tools: None,
        admin: false,
    };

    // All tools should be denied with empty allowlist
    assert!(client.check_tool_scope("server", "search_web").is_err());
    assert!(client.check_tool_scope("server", "read_file").is_err());
}

/// Test empty denylist (allow all that pass other checks)
#[test]
fn test_empty_denylist() {
    let client = AuthenticatedClient {
        name: "allow_all".to_string(),
        rate_limit: 0,
        backends: vec![],
        allowed_tools: None,
        denied_tools: Some(vec![]), // Empty denylist = no additional blocks
        admin: false,
    };

    // Empty denylist should not block anything (falls back to global policy)
    assert!(client.check_tool_scope("server", "search_web").is_ok());
    assert!(client.check_tool_scope("server", "read_file").is_ok());
}

/// Test pattern matching edge cases
#[test]
fn test_pattern_matching_edge_cases() {
    let client = AuthenticatedClient {
        name: "edge_cases".to_string(),
        rate_limit: 0,
        backends: vec![],
        allowed_tools: Some(vec![
            "a*".to_string(),
            "exact_match".to_string(),
            "*ends_with_this".to_string(), // This should NOT match (only suffix * supported)
        ]),
        denied_tools: None,
        admin: false,
    };

    // Prefix glob works
    assert!(client.check_tool_scope("server", "abc").is_ok());
    assert!(client.check_tool_scope("server", "a").is_ok());

    // Exact match works
    assert!(client.check_tool_scope("server", "exact_match").is_ok());

    // Prefix/suffix glob (only suffix * is implemented, so this is exact match)
    // The pattern "*ends_with_this" will only match if tool name is exactly "*ends_with_this"
    assert!(
        client
            .check_tool_scope("server", "starts_with_this_ends_with_this")
            .is_err()
    );

    // Not matching any pattern
    assert!(client.check_tool_scope("server", "b").is_err());
}
