mod executor_tests {
    use fraiseql_core::schema::FieldType;

    use super::super::executor::{graphql_value, is_scalar_field_type, is_valid_graphql_name};

    #[test]
    fn test_graphql_value_string() {
        let v = serde_json::Value::String("hello".to_string());
        assert_eq!(graphql_value(&v), "\"hello\"");
    }

    #[test]
    fn test_graphql_value_string_escapes_quotes() {
        let v = serde_json::Value::String("say \"hi\"".to_string());
        assert_eq!(graphql_value(&v), r#""say \"hi\"""#);
    }

    #[test]
    fn test_graphql_value_string_escapes_backslash() {
        let v = serde_json::Value::String(r"a\b".to_string());
        assert_eq!(graphql_value(&v), r#""a\\b""#);
    }

    #[test]
    fn test_graphql_value_string_escapes_newline() {
        let v = serde_json::Value::String("line1\nline2".to_string());
        assert_eq!(graphql_value(&v), "\"line1\\nline2\"");
    }

    #[test]
    fn test_is_valid_graphql_name() {
        assert!(is_valid_graphql_name("limit"));
        assert!(is_valid_graphql_name("_private"));
        assert!(is_valid_graphql_name("field1"));
        assert!(!is_valid_graphql_name(""));
        assert!(!is_valid_graphql_name("1abc"));
        assert!(!is_valid_graphql_name("has space"));
        assert!(!is_valid_graphql_name("inject: bad"));
    }

    #[test]
    fn test_graphql_value_number() {
        let v = serde_json::json!(42);
        assert_eq!(graphql_value(&v), "42");
    }

    #[test]
    fn test_graphql_value_bool() {
        let v = serde_json::Value::Bool(true);
        assert_eq!(graphql_value(&v), "true");
    }

    #[test]
    fn test_graphql_value_array() {
        let v = serde_json::json!([1, 2, 3]);
        assert_eq!(graphql_value(&v), "[1, 2, 3]");
    }

    #[test]
    fn test_is_scalar_field_type() {
        assert!(is_scalar_field_type(&FieldType::String));
        assert!(is_scalar_field_type(&FieldType::Int));
        assert!(is_scalar_field_type(&FieldType::List(Box::new(FieldType::Int))));
        assert!(!is_scalar_field_type(&FieldType::Object("User".to_string())));
    }
}

mod tools_tests {
    #![allow(clippy::unwrap_used)]

    use fraiseql_core::schema::{ArgumentDefinition, FieldType};

    use super::super::{
        McpConfig,
        tools::{arguments_to_json_schema, field_type_to_json_schema, should_include},
    };

    fn make_config(include: Vec<String>, exclude: Vec<String>) -> McpConfig {
        McpConfig {
            enabled: true,
            transport: "http".to_string(),
            path: "/mcp".to_string(),
            require_auth: true,
            include,
            exclude,
        }
    }

    #[test]
    fn test_should_include_all_when_empty() {
        let config = make_config(vec![], vec![]);
        assert!(should_include("users", &config));
        assert!(should_include("createUser", &config));
    }

    #[test]
    fn test_should_include_whitelist() {
        let config = make_config(vec!["users".to_string()], vec![]);
        assert!(should_include("users", &config));
        assert!(!should_include("createUser", &config));
    }

    #[test]
    fn test_should_include_blacklist() {
        let config = make_config(vec![], vec!["createUser".to_string()]);
        assert!(should_include("users", &config));
        assert!(!should_include("createUser", &config));
    }

    #[test]
    fn test_field_type_to_json_schema() {
        let schema = field_type_to_json_schema(&FieldType::String);
        assert_eq!(schema, serde_json::json!({ "type": "string" }));

        let schema = field_type_to_json_schema(&FieldType::Int);
        assert_eq!(schema, serde_json::json!({ "type": "integer" }));

        let schema = field_type_to_json_schema(&FieldType::Boolean);
        assert_eq!(schema, serde_json::json!({ "type": "boolean" }));

        let schema = field_type_to_json_schema(&FieldType::List(Box::new(FieldType::Int)));
        assert_eq!(schema, serde_json::json!({ "type": "array", "items": { "type": "integer" } }));
    }

    #[test]
    fn test_arguments_to_json_schema() {
        let args = vec![
            ArgumentDefinition::new("id", FieldType::Id),
            ArgumentDefinition::optional("name", FieldType::String),
        ];

        let schema = arguments_to_json_schema(&args);
        let props = schema.get("properties").unwrap().as_object().unwrap();
        assert!(props.contains_key("id"));
        assert!(props.contains_key("name"));

        let required = schema.get("required").unwrap().as_array().unwrap();
        assert_eq!(required.len(), 1);
        assert_eq!(required[0], "id");
    }
}

mod handler_tests {
    #![allow(clippy::unwrap_used)] // Reason: test code, panics are acceptable

    use super::super::handler::extract_bearer;

    #[test]
    fn extract_bearer_returns_token_for_well_formed_header() {
        let mut headers = http::HeaderMap::new();
        headers.insert(http::header::AUTHORIZATION, "Bearer abc.def.ghi".parse().unwrap());
        assert_eq!(extract_bearer(&headers), Some("abc.def.ghi".to_string()));
    }

    #[test]
    fn extract_bearer_none_when_header_missing() {
        assert_eq!(extract_bearer(&http::HeaderMap::new()), None);
    }

    #[test]
    fn extract_bearer_none_for_non_bearer_scheme() {
        let mut headers = http::HeaderMap::new();
        headers.insert(http::header::AUTHORIZATION, "Basic dXNlcjpwYXNz".parse().unwrap());
        assert_eq!(extract_bearer(&headers), None);
    }

    #[test]
    fn extract_bearer_none_for_empty_token() {
        let mut headers = http::HeaderMap::new();
        headers.insert(http::header::AUTHORIZATION, "Bearer    ".parse().unwrap());
        assert_eq!(extract_bearer(&headers), None);
    }
}
