use super::*;
use crate::config::LintConfig;
use crate::diagnostics::DiagnosticLevel;

fn validate(content: &str) -> Vec<Diagnostic> {
    let validator = HooksValidator;
    validator.validate(Path::new("settings.json"), content, &LintConfig::default())
}

fn validate_with_config(content: &str, config: &LintConfig) -> Vec<Diagnostic> {
    let validator = HooksValidator;
    validator.validate(Path::new("settings.json"), content, config)
}

#[test]
fn test_cc_hk_006_command_hook_missing_command() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_006: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-006")
        .collect();

    assert_eq!(cc_hk_006.len(), 1);
    assert_eq!(cc_hk_006[0].level, DiagnosticLevel::Error);
    assert!(
        cc_hk_006[0]
            .message
            .contains("missing required 'command' field")
    );
}

#[test]
fn test_cc_hk_006_command_hook_with_command_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo hello" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_006: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-006")
        .collect();

    assert_eq!(cc_hk_006.len(), 0);
}

#[test]
fn test_cc_hk_006_multiple_command_hooks_missing_command() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "*",
                        "hooks": [
                            { "type": "command" },
                            { "type": "command", "command": "valid" },
                            { "type": "command" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_006: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-006")
        .collect();

    assert_eq!(cc_hk_006.len(), 2);
}

#[test]
fn test_cc_hk_007_prompt_hook_missing_prompt() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "prompt" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_007: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-007")
        .collect();

    assert_eq!(cc_hk_007.len(), 1);
    assert_eq!(cc_hk_007[0].level, DiagnosticLevel::Error);
    assert!(
        cc_hk_007[0]
            .message
            .contains("missing required 'prompt' field")
    );
}

#[test]
fn test_cc_hk_007_prompt_hook_with_prompt_ok() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "Summarize the session" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_007: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-007")
        .collect();

    assert_eq!(cc_hk_007.len(), 0);
}

#[test]
fn test_cc_hk_007_mixed_hooks_one_missing_prompt() {
    let content = r#"{
            "hooks": {
                "SubagentStop": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "valid prompt" },
                            { "type": "prompt" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_007: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-007")
        .collect();

    assert_eq!(cc_hk_007.len(), 1);
}

#[test]
fn test_cc_hk_008_script_file_not_found() {
    let content = r#"{
            "hooks": {
                "SessionStart": [
                    {
                        "hooks": [
                            { "type": "command", "command": "bash scripts/nonexistent.sh" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_008: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-008")
        .collect();

    assert_eq!(cc_hk_008.len(), 1);
    assert_eq!(cc_hk_008[0].level, DiagnosticLevel::Error);
    assert!(cc_hk_008[0].message.contains("Script file not found"));
}

#[test]
fn test_cc_hk_008_system_command_no_script_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo 'logging tool use'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_008: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-008")
        .collect();

    assert_eq!(cc_hk_008.len(), 0);
}

#[test]
fn test_cc_hk_008_env_var_with_unresolvable_path_skipped() {
    let content = r#"{
            "hooks": {
                "SessionStart": [
                    {
                        "hooks": [
                            { "type": "command", "command": "$HOME/scripts/setup.sh" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_008: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-008")
        .collect();

    assert_eq!(cc_hk_008.len(), 0);
}

#[test]
fn test_cc_hk_008_python_script_not_found() {
    let content = r#"{
            "hooks": {
                "PostToolUse": [
                    {
                        "matcher": "*",
                        "hooks": [
                            { "type": "command", "command": "python hooks/logger.py" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_008: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-008")
        .collect();

    assert_eq!(cc_hk_008.len(), 1);
    assert!(cc_hk_008[0].message.contains("logger.py"));
}

#[test]
fn test_cc_hk_008_tilde_home_path_does_not_false_positive() {
    // Regression for #894: Claude Code passes hook commands to the shell,
    // which expands ~/... to $HOME/... at runtime. agnix previously
    // resolved ~/.claude/hooks/hook.py against the project dir and flagged
    // it as "not found (resolved to /cwd/~/.claude/hooks/hook.py)".
    // After the fix, CC-HK-008 either (a) expands ~/ to the actual HOME
    // and checks there, or (b) treats it as unresolved if HOME isn't
    // readable - neither path should emit a spurious "file not found"
    // for the ~/... prefix on a machine where HOME is available but the
    // expanded path may or may not exist.
    let content = r#"{
            "hooks": {
                "PostToolUse": [
                    {
                        "matcher": "Edit",
                        "hooks": [
                            { "type": "command", "command": "~/.claude/hooks/dippy-hook.py", "timeout": 5 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_008: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-008")
        .collect();

    // The diagnostic, if any, must not reference the literal "~" prefix
    // in the resolved path - that's the exact false-positive shape from
    // the bug report. With a reachable $HOME the path will either resolve
    // under $HOME or be skipped; without one the whole path is treated as
    // unresolved and no diagnostic fires.
    for d in &cc_hk_008 {
        assert!(
            !d.message.contains("/~/"),
            "CC-HK-008 must not emit a diagnostic that concatenates cwd with literal `~/`; got: {}",
            d.message
        );
    }
}

#[test]
fn test_cc_hk_008_tilde_user_path_is_skipped() {
    // `~user/...` forms require resolving arbitrary user home directories
    // via nss/passwd, which agnix does not do. Skip the existence check
    // to avoid false-positives on shared-infra setups where a hook runs
    // as a different user.
    let content = r#"{
            "hooks": {
                "SessionStart": [
                    {
                        "hooks": [
                            { "type": "command", "command": "~alice/.local/bin/setup.sh" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_008: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-008")
        .collect();

    assert_eq!(
        cc_hk_008.len(),
        0,
        "CC-HK-008 must not fire on ~user/... paths (got: {:?})",
        cc_hk_008.iter().map(|d| &d.message).collect::<Vec<_>>()
    );
}

#[test]
fn test_cc_hk_008_url_not_treated_as_script() {
    let content = r#"{
            "hooks": {
                "Setup": [
                    {
                        "hooks": [
                            { "type": "command", "command": "curl https://example.com/install.sh | bash" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_008: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-008")
        .collect();

    assert_eq!(cc_hk_008.len(), 0);
}

#[test]
fn test_cc_hk_009_rm_rf_root() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "command", "command": "rm -rf /" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_009: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-009")
        .collect();

    assert_eq!(cc_hk_009.len(), 1);
    assert_eq!(cc_hk_009[0].level, DiagnosticLevel::Warning);
    assert!(cc_hk_009[0].message.contains("dangerous"));
}

#[test]
fn test_cc_hk_009_git_reset_hard() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Write",
                        "hooks": [
                            { "type": "command", "command": "git reset --hard HEAD" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_009: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-009")
        .collect();

    assert_eq!(cc_hk_009.len(), 1);
    assert!(cc_hk_009[0].message.contains("Hard reset"));
}

#[test]
fn test_cc_hk_009_curl_pipe_bash() {
    let content = r#"{
            "hooks": {
                "Setup": [
                    {
                        "hooks": [
                            { "type": "command", "command": "curl https://example.com/install.sh | bash" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_009: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-009")
        .collect();

    assert_eq!(cc_hk_009.len(), 1);
    assert!(cc_hk_009[0].message.contains("security risk"));
}

#[test]
fn test_cc_hk_009_git_push_force() {
    let content = r#"{
            "hooks": {
                "PostToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "git push origin main --force" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_009: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-009")
        .collect();

    assert_eq!(cc_hk_009.len(), 1);
    assert!(cc_hk_009[0].message.contains("Force push"));
}

#[test]
fn test_cc_hk_009_drop_database() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "command", "command": "psql -c 'DROP DATABASE production'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_009: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-009")
        .collect();

    assert_eq!(cc_hk_009.len(), 1);
    assert!(cc_hk_009[0].message.contains("irreversible"));
}

#[test]
fn test_cc_hk_009_chmod_777() {
    let content = r#"{
            "hooks": {
                "Setup": [
                    {
                        "hooks": [
                            { "type": "command", "command": "chmod 777 /var/www" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_009: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-009")
        .collect();

    assert_eq!(cc_hk_009.len(), 1);
    assert!(cc_hk_009[0].message.contains("full access"));
}

#[test]
fn test_cc_hk_009_safe_command_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo 'logging'" },
                            { "type": "command", "command": "git status" },
                            { "type": "command", "command": "npm test" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_009: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-009")
        .collect();

    assert_eq!(cc_hk_009.len(), 0);
}

#[test]
fn test_valid_hooks_no_errors() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo 'pre-bash'" }
                        ]
                    }
                ],
                "Stop": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "Summarize the work done" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);

    let rule_errors: Vec<_> = diagnostics
        .iter()
        .filter(|d| {
            d.rule.starts_with("CC-HK-006")
                || d.rule.starts_with("CC-HK-007")
                || d.rule.starts_with("CC-HK-009")
        })
        .collect();

    assert_eq!(rule_errors.len(), 0);
}

#[test]
fn test_empty_hooks_ok() {
    let content = r#"{ "hooks": {} }"#;

    let diagnostics = validate(content);

    assert!(diagnostics.is_empty());
}

#[test]
fn test_settings_with_other_fields() {
    let content = r#"{
            "permissions": { "allow": ["Read"] },
            "hooks": {
                "SessionStart": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo 'started'" }
                        ]
                    }
                ]
            },
            "model": "sonnet"
        }"#;

    let diagnostics = validate(content);

    let parse_errors: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-012")
        .collect();
    assert_eq!(parse_errors.len(), 0);
}

#[test]
fn test_invalid_json_parse_error() {
    let content = r#"{ invalid json }"#;

    let diagnostics = validate(content);

    let parse_errors: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-012")
        .collect();
    assert_eq!(parse_errors.len(), 1);
}

#[test]
fn test_extract_script_paths_sh() {
    let validator = HooksValidator;
    let paths = validator.extract_script_paths("bash scripts/hook.sh");
    assert_eq!(paths, vec!["scripts/hook.sh"]);
}

#[test]
fn test_extract_script_paths_py() {
    let validator = HooksValidator;
    let paths = validator.extract_script_paths("python /path/to/script.py arg1 arg2");
    assert_eq!(paths, vec!["/path/to/script.py"]);
}

#[test]
fn test_extract_script_paths_env_var() {
    let validator = HooksValidator;
    let paths = validator.extract_script_paths("$CLAUDE_PROJECT_DIR/hooks/setup.sh");
    assert_eq!(paths, vec!["$CLAUDE_PROJECT_DIR/hooks/setup.sh"]);
}

#[test]
fn test_extract_script_paths_no_script() {
    let validator = HooksValidator;
    let paths = validator.extract_script_paths("echo 'hello world'");
    assert!(paths.is_empty());
}

#[test]
fn test_extract_script_paths_multiple() {
    let validator = HooksValidator;
    let paths = validator.extract_script_paths("./first.sh && ./second.sh");
    assert_eq!(paths.len(), 2);
    assert!(paths.contains(&"./first.sh".to_string()));
    assert!(paths.contains(&"./second.sh".to_string()));
}

#[test]
fn test_extract_script_paths_quoted() {
    let validator = HooksValidator;
    let paths = validator.extract_script_paths("bash \"$CLAUDE_PROJECT_DIR/hooks/test.sh\"");
    assert_eq!(paths, vec!["$CLAUDE_PROJECT_DIR/hooks/test.sh"]);
}

#[test]
fn test_has_unresolved_env_vars() {
    let validator = HooksValidator;
    assert!(!validator.has_unresolved_env_vars("./script.sh"));
    assert!(!validator.has_unresolved_env_vars("$CLAUDE_PROJECT_DIR/script.sh"));
    assert!(validator.has_unresolved_env_vars("$HOME/script.sh"));
    assert!(validator.has_unresolved_env_vars("$CLAUDE_PROJECT_DIR/$HOME/script.sh"));
}

#[test]
fn test_dangerous_pattern_case_insensitive() {
    let validator = HooksValidator;

    assert!(validator.check_dangerous_patterns("RM -RF /").is_some());
    assert!(
        validator
            .check_dangerous_patterns("Git Reset --Hard")
            .is_some()
    );
    assert!(
        validator
            .check_dangerous_patterns("DROP DATABASE test")
            .is_some()
    );
}

#[test]
fn test_fixture_valid_settings() {
    let content = include_str!("../../../../../tests/fixtures/valid/hooks/settings.json");
    let diagnostics = validate(content);
    let errors: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule.starts_with("CC-HK-00"))
        .collect();
    assert!(errors.is_empty());
}

#[test]
fn test_fixture_missing_command() {
    let content = include_str!(
        "../../../../../tests/fixtures/invalid/hooks/missing-command-field/settings.json"
    );
    let diagnostics = validate(content);
    let cc_hk_006: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-006")
        .collect();
    assert!(!cc_hk_006.is_empty());
}

#[test]
fn test_fixture_missing_prompt() {
    let content = include_str!(
        "../../../../../tests/fixtures/invalid/hooks/missing-prompt-field/settings.json"
    );
    let diagnostics = validate(content);
    let cc_hk_007: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-007")
        .collect();
    assert!(!cc_hk_007.is_empty());
}

#[test]
fn test_fixture_dangerous_commands() {
    let content = include_str!(
        "../../../../../tests/fixtures/invalid/hooks/dangerous-commands/settings.json"
    );
    let diagnostics = validate(content);
    let cc_hk_009: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-009")
        .collect();
    assert!(cc_hk_009.len() >= 3);
}

// ===== CC-HK-001 Tests: Invalid Event Name =====

#[test]
fn test_cc_hk_001_invalid_event_name() {
    let content = r#"{
            "hooks": {
                "InvalidEvent": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();

    assert_eq!(cc_hk_001.len(), 1);
    assert_eq!(cc_hk_001[0].level, DiagnosticLevel::Error);
    assert!(cc_hk_001[0].message.contains("Invalid hook event"));
    assert!(cc_hk_001[0].message.contains("InvalidEvent"));
}

#[test]
fn test_cc_hk_001_wrong_case_event_name() {
    let content = r#"{
            "hooks": {
                "pretooluse": [
                    {
                        "matcher": "*",
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();

    assert_eq!(cc_hk_001.len(), 1);
    // Should suggest the correct case
    assert!(
        cc_hk_001[0]
            .suggestion
            .as_ref()
            .unwrap()
            .contains("PreToolUse")
    );
    assert!(
        cc_hk_001[0]
            .suggestion
            .as_ref()
            .unwrap()
            .contains("case-sensitive")
    );
}

#[test]
fn test_cc_hk_001_valid_event_name() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();

    assert_eq!(cc_hk_001.len(), 0);
}

#[test]
fn test_cc_hk_001_message_display_event_valid() {
    // MessageDisplay hook event added in Claude Code v2.1.152: lets hooks
    // transform or hide assistant message text as it is displayed.
    let content = r#"{
            "hooks": {
                "MessageDisplay": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();

    assert_eq!(
        cc_hk_001.len(),
        0,
        "MessageDisplay is a valid Claude Code hook event and must not flag CC-HK-001"
    );
}

#[test]
fn test_cc_hk_001_multiple_invalid_events() {
    let content = r#"{
            "hooks": {
                "InvalidOne": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ],
                "InvalidTwo": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();

    assert_eq!(cc_hk_001.len(), 2);
}

#[test]
fn test_fixture_invalid_event() {
    let content =
        include_str!("../../../../../tests/fixtures/invalid/hooks/invalid-event/settings.json");
    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();
    // "InvalidEvent" and "pretooluse" are invalid
    assert_eq!(cc_hk_001.len(), 2);
}

// ===== CC-HK-002 Tests: Prompt Hook on Wrong Event =====

#[test]
fn test_cc_hk_002_prompt_on_pretooluse_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "prompt", "prompt": "allowed here" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_002: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-002")
        .collect();

    assert_eq!(cc_hk_002.len(), 0);
}

#[test]
fn test_cc_hk_002_prompt_on_session_start() {
    let content = r#"{
            "hooks": {
                "SessionStart": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "not allowed here" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_002: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-002")
        .collect();

    assert_eq!(cc_hk_002.len(), 1);
    assert_eq!(cc_hk_002[0].level, DiagnosticLevel::Error);
    assert!(cc_hk_002[0].message.contains("not supported on"));
}

#[test]
fn test_cc_hk_002_prompt_on_stop_ok() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "this is valid" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_002: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-002")
        .collect();

    assert_eq!(cc_hk_002.len(), 0);
}

#[test]
fn test_cc_hk_002_prompt_on_subagent_stop_ok() {
    let content = r#"{
            "hooks": {
                "SubagentStop": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "this is valid" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_002: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-002")
        .collect();

    assert_eq!(cc_hk_002.len(), 0);
}

#[test]
fn test_fixture_prompt_on_wrong_event() {
    let content = include_str!(
        "../../../../../tests/fixtures/invalid/hooks/prompt-on-wrong-event/settings.json"
    );
    let diagnostics = validate(content);
    let cc_hk_002: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-002")
        .collect();
    // SessionStart and Notification should trigger errors, Stop and PreToolUse should not
    assert_eq!(cc_hk_002.len(), 2);
}

// ===== CC-HK-003 Tests: Matcher Hint for Tool Events =====

#[test]
fn test_cc_hk_003_missing_matcher_pretooluse() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_003: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-003")
        .collect();

    assert_eq!(cc_hk_003.len(), 1);
    assert_eq!(cc_hk_003[0].level, DiagnosticLevel::Info);
    assert!(cc_hk_003[0].message.contains("has no matcher"));
}

#[test]
fn test_cc_hk_003_missing_matcher_permission_request() {
    let content = r#"{
            "hooks": {
                "PermissionRequest": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_003: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-003")
        .collect();

    assert_eq!(cc_hk_003.len(), 1);
    assert_eq!(cc_hk_003[0].level, DiagnosticLevel::Info);
    assert!(cc_hk_003[0].message.contains("has no matcher"));
}

#[test]
fn test_cc_hk_003_missing_matcher_posttooluse() {
    let content = r#"{
            "hooks": {
                "PostToolUse": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_003: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-003")
        .collect();

    assert_eq!(cc_hk_003.len(), 1);
    assert_eq!(cc_hk_003[0].level, DiagnosticLevel::Info);
    assert!(cc_hk_003[0].message.contains("has no matcher"));
}

#[test]
fn test_cc_hk_003_missing_matcher_posttoolusefailure() {
    let content = r#"{
            "hooks": {
                "PostToolUseFailure": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_003: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-003")
        .collect();

    assert_eq!(cc_hk_003.len(), 1);
    assert_eq!(cc_hk_003[0].level, DiagnosticLevel::Info);
    assert!(cc_hk_003[0].message.contains("has no matcher"));
}

#[test]
fn test_cc_hk_003_with_matcher_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "*",
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_003: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-003")
        .collect();

    assert_eq!(cc_hk_003.len(), 0);
}

#[test]
fn test_fixture_missing_matcher() {
    let content =
        include_str!("../../../../../tests/fixtures/invalid/hooks/missing-matcher/settings.json");
    let diagnostics = validate(content);
    let cc_hk_003: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-003")
        .collect();
    // All 4 tool events without matchers
    assert_eq!(cc_hk_003.len(), 4);
    assert!(
        cc_hk_003.iter().all(|d| d.level == DiagnosticLevel::Info),
        "All CC-HK-003 diagnostics should be Info level"
    );
}

// ===== CC-HK-004 Tests: Matcher on Non-Tool Event =====

#[test]
fn test_cc_hk_004_matcher_on_stop() {
    // Stop events with matchers are handled by CC-HK-018 (info), not CC-HK-004 (error)
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_004: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-004")
        .collect();
    let cc_hk_018: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-018")
        .collect();

    assert_eq!(cc_hk_004.len(), 0, "Stop should not trigger CC-HK-004");
    assert_eq!(cc_hk_018.len(), 1, "Stop should trigger CC-HK-018");
    assert_eq!(cc_hk_018[0].level, DiagnosticLevel::Info);
}

#[test]
fn test_cc_hk_004_matcher_on_session_start() {
    // SessionStart now supports matchers - no CC-HK-004
    let content = r#"{
            "hooks": {
                "SessionStart": [
                    {
                        "matcher": "startup",
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_004: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-004")
        .collect();

    assert_eq!(cc_hk_004.len(), 0, "SessionStart now supports matchers");
}

#[test]
fn test_cc_hk_004_no_matcher_on_stop_ok() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_004: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-004")
        .collect();

    assert_eq!(cc_hk_004.len(), 0);
}

#[test]
fn test_cc_hk_004_has_safe_fix_when_unique_matcher_line() {
    let content = r#"{
            "hooks": {
                "TaskCompleted": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_004 = diagnostics
        .iter()
        .find(|d| d.rule == "CC-HK-004")
        .expect("CC-HK-004 should be reported");

    assert!(cc_hk_004.has_fixes());
    let fix = &cc_hk_004.fixes[0];
    assert!(fix.safe);
    assert_eq!(fix.replacement, "");
}

#[test]
fn test_fixture_matcher_on_wrong_event() {
    let content = include_str!(
        "../../../../../tests/fixtures/invalid/hooks/matcher-on-wrong-event/settings.json"
    );
    let diagnostics = validate(content);
    let cc_hk_004: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-004")
        .collect();
    // SubagentStop and SessionStart now support matchers - no CC-HK-004
    // Stop and UserPromptSubmit are handled by CC-HK-018 instead
    assert_eq!(cc_hk_004.len(), 0);

    let cc_hk_018: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-018")
        .collect();
    // Stop and UserPromptSubmit trigger CC-HK-018
    assert_eq!(cc_hk_018.len(), 2);
}

// ===== CC-HK-005 Tests: Missing Type Field =====

#[test]
fn test_cc_hk_005_missing_type_field() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "command": "echo 'missing type'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_005: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-005")
        .collect();

    assert_eq!(cc_hk_005.len(), 1);
    assert_eq!(cc_hk_005[0].level, DiagnosticLevel::Error);
    assert!(
        cc_hk_005[0]
            .message
            .contains("missing required 'type' field")
    );
}

#[test]
fn test_cc_hk_005_multiple_missing_type() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "command": "echo 'missing type 1'" },
                            { "prompt": "missing type 2" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_005: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-005")
        .collect();

    assert_eq!(cc_hk_005.len(), 2);
}

#[test]
fn test_cc_hk_005_with_type_ok() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo 'has type'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_005: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-005")
        .collect();

    assert_eq!(cc_hk_005.len(), 0);
}

#[test]
fn test_fixture_missing_type_field() {
    let content = include_str!(
        "../../../../../tests/fixtures/invalid/hooks/missing-type-field/settings.json"
    );
    let diagnostics = validate(content);
    let cc_hk_005: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-005")
        .collect();
    // 3 hooks missing type field
    assert_eq!(cc_hk_005.len(), 3);
}

// ===== Helper Function Tests =====

#[test]
fn test_find_closest_event_exact_case_match() {
    let closest = find_closest_event("pretooluse");
    assert!(closest.suggestion.contains("PreToolUse"));
    assert!(closest.suggestion.contains("case-sensitive"));
    assert_eq!(closest.corrected_event, Some("PreToolUse".to_string()));
    assert!(closest.is_case_fix);
}

#[test]
fn test_find_closest_event_partial_match() {
    let closest = find_closest_event("tool");
    assert!(closest.suggestion.contains("Did you mean"));
    assert!(closest.corrected_event.is_some());
    assert!(!closest.is_case_fix);
}

#[test]
fn test_find_closest_event_no_match() {
    let closest = find_closest_event("CompletelyInvalid");
    assert!(closest.suggestion.contains("Valid events are"));
    assert!(closest.corrected_event.is_none());
}

// ===== CC-HK-001 Auto-fix Tests =====

#[test]
fn test_cc_hk_001_case_fix_has_safe_fix() {
    let content = r#"{
            "hooks": {
                "pretooluse": [
                    {
                        "matcher": "*",
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();

    assert_eq!(cc_hk_001.len(), 1);
    assert!(cc_hk_001[0].has_fixes());

    let fix = &cc_hk_001[0].fixes[0];
    assert!(fix.safe); // Case-only fix is safe
    assert_eq!(fix.replacement, "\"PreToolUse\"");
}

#[test]
fn test_cc_hk_001_case_fix_teammateidle() {
    let content = r#"{
            "hooks": {
                "teammateidle": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo idle" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();

    assert_eq!(cc_hk_001.len(), 1);
    assert!(cc_hk_001[0].has_fixes());

    let fix = &cc_hk_001[0].fixes[0];
    assert!(fix.safe); // Case-only fix is safe
    assert_eq!(fix.replacement, "\"TeammateIdle\"");
}

#[test]
fn test_cc_hk_001_case_fix_taskcompleted() {
    let content = r#"{
            "hooks": {
                "taskcompleted": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo done" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();

    assert_eq!(cc_hk_001.len(), 1);
    assert!(cc_hk_001[0].has_fixes());

    let fix = &cc_hk_001[0].fixes[0];
    assert!(fix.safe); // Case-only fix is safe
    assert_eq!(fix.replacement, "\"TaskCompleted\"");
}

#[test]
fn test_cc_hk_001_typo_teammate_partial_match() {
    // "Teammate" partially matches "TeammateIdle" - should produce unsafe fix
    let content = r#"{
            "hooks": {
                "Teammate": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo idle" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();

    assert_eq!(cc_hk_001.len(), 1);
    assert!(cc_hk_001[0].has_fixes());

    let fix = &cc_hk_001[0].fixes[0];
    assert!(!fix.safe); // Partial match is not safe
    assert_eq!(fix.replacement, "\"TeammateIdle\"");
}

#[test]
fn test_cc_hk_001_typo_fix_not_safe() {
    // "tool" partially matches "PreToolUse"
    let content = r#"{
            "hooks": {
                "tool": [
                    {
                        "matcher": "*",
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();

    assert_eq!(cc_hk_001.len(), 1);
    assert!(cc_hk_001[0].has_fixes());

    let fix = &cc_hk_001[0].fixes[0];
    assert!(!fix.safe); // Partial match is not safe
}

#[test]
fn test_cc_hk_001_no_fix_for_completely_invalid() {
    let content = r#"{
            "hooks": {
                "XyzAbc123": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo 'test'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();

    assert_eq!(cc_hk_001.len(), 1);
    // No fix when there's no reasonable match
    assert!(!cc_hk_001[0].has_fixes());
}

#[test]
fn test_cc_hk_001_fix_correct_byte_position() {
    let content = r#"{"hooks": {"stop": [{"hooks": [{"type": "command", "command": "echo"}]}]}}"#;

    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();

    assert_eq!(cc_hk_001.len(), 1);
    assert!(cc_hk_001[0].has_fixes());

    let fix = &cc_hk_001[0].fixes[0];

    // Apply fix and verify
    let mut fixed = content.to_string();
    fixed.replace_range(fix.start_byte..fix.end_byte, &fix.replacement);
    assert!(fixed.contains("\"Stop\""));
    assert!(!fixed.contains("\"stop\""));
}

#[test]
fn test_find_event_key_position() {
    let content = r#"{"hooks": {"InvalidEvent": []}}"#;
    let pos = find_event_key_position(content, "InvalidEvent");
    assert!(pos.is_some());
    let (start, end) = pos.unwrap();
    assert_eq!(&content[start..end], "\"InvalidEvent\"");
}

#[test]
fn test_find_event_key_position_not_found() {
    let content = r#"{"hooks": {"ValidEvent": []}}"#;
    let pos = find_event_key_position(content, "NotPresent");
    assert!(pos.is_none());
}

// ===== CC-HK-010 Tests: No Timeout Specified =====

#[test]
fn test_cc_hk_010_command_hook_no_timeout() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();

    assert_eq!(cc_hk_010.len(), 1);
    assert_eq!(cc_hk_010[0].level, DiagnosticLevel::Warning);
    assert!(cc_hk_010[0].message.contains("no timeout specified"));
}

#[test]
fn test_cc_hk_010_prompt_hook_no_timeout() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "Summarize session" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();

    assert_eq!(cc_hk_010.len(), 1);
    assert_eq!(cc_hk_010[0].level, DiagnosticLevel::Warning);
    assert!(cc_hk_010[0].message.contains("Prompt/agent hook"));
}

#[test]
fn test_cc_hk_010_with_timeout_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test", "timeout": 30 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();

    assert_eq!(cc_hk_010.len(), 0);
}

#[test]
fn test_cc_hk_010_command_timeout_exceeds_default() {
    // Command hooks have 600s default - 700 should warn
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test", "timeout": 700 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();

    assert_eq!(cc_hk_010.len(), 1);
    assert_eq!(cc_hk_010[0].level, DiagnosticLevel::Warning);
    assert!(cc_hk_010[0].message.contains("exceeding 600s default"));
}

#[test]
fn test_cc_hk_010_command_timeout_at_default_ok() {
    // Command hooks have 600s default - 600 should NOT warn
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test", "timeout": 600 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();

    assert_eq!(cc_hk_010.len(), 0);
}

#[test]
fn test_cc_hk_010_prompt_timeout_exceeds_default() {
    // Prompt hooks have 30s default - 45 should warn
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "test prompt", "timeout": 45 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();

    assert_eq!(cc_hk_010.len(), 1);
    assert_eq!(cc_hk_010[0].level, DiagnosticLevel::Warning);
    assert!(cc_hk_010[0].message.contains("exceeding 30s default"));
}

#[test]
fn test_cc_hk_010_prompt_timeout_at_default_ok() {
    // Prompt hooks have 30s default - 30 should NOT warn
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "test prompt", "timeout": 30 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();

    assert_eq!(cc_hk_010.len(), 0);
}

#[test]
fn test_cc_hk_010_multiple_hooks_mixed() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "*",
                        "hooks": [
                            { "type": "command", "command": "echo 'no timeout'" },
                            { "type": "command", "command": "echo 'with timeout'", "timeout": 30 },
                            { "type": "command", "command": "echo 'also no timeout'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();

    assert_eq!(cc_hk_010.len(), 2);
}

#[test]
fn test_fixture_no_timeout() {
    let content =
        include_str!("../../../../../tests/fixtures/invalid/hooks/no-timeout/settings.json");
    let diagnostics = validate(content);
    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();
    // PreToolUse command and Stop prompt are missing timeout
    assert_eq!(cc_hk_010.len(), 2);
}

// ===== CC-HK-011 Tests: Invalid Timeout Value =====

#[test]
fn test_cc_hk_011_negative_timeout() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test", "timeout": -5 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_011: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-011")
        .collect();

    assert_eq!(cc_hk_011.len(), 1);
    assert_eq!(cc_hk_011[0].level, DiagnosticLevel::Error);
    assert!(cc_hk_011[0].message.contains("Invalid timeout"));
}

#[test]
fn test_cc_hk_011_has_unsafe_fix_for_invalid_timeout() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test", "timeout": -5 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_011 = diagnostics
        .iter()
        .find(|d| d.rule == "CC-HK-011")
        .expect("CC-HK-011 should be reported");

    assert!(cc_hk_011.has_fixes());
    let fix = &cc_hk_011.fixes[0];
    assert_eq!(fix.replacement, "30");
    assert!(!fix.safe);
}

#[test]
fn test_cc_hk_011_zero_timeout() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test", "timeout": 0 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_011: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-011")
        .collect();

    assert_eq!(cc_hk_011.len(), 1);
    assert_eq!(cc_hk_011[0].level, DiagnosticLevel::Error);
}

#[test]
fn test_cc_hk_011_float_zero_timeout() {
    // Edge case: 0.0 should be treated as invalid (zero is not positive)
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test", "timeout": 0.0 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_011: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-011")
        .collect();

    assert_eq!(cc_hk_011.len(), 1);
    assert_eq!(cc_hk_011[0].level, DiagnosticLevel::Error);
}

#[test]
fn test_cc_hk_011_float_timeout() {
    // Non-integer floats like 30.5 should be invalid
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test", "timeout": 30.5 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_011: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-011")
        .collect();

    assert_eq!(cc_hk_011.len(), 1);
    assert_eq!(cc_hk_011[0].level, DiagnosticLevel::Error);
}

#[test]
fn test_cc_hk_011_whole_float_invalid() {
    // Even whole floats like 30.0 are invalid - must be integer, not float
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test", "timeout": 30.0 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_011: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-011")
        .collect();

    // 30.0 is a float, not an integer - rule requires positive INTEGER
    assert_eq!(cc_hk_011.len(), 1);
    assert_eq!(cc_hk_011[0].level, DiagnosticLevel::Error);
}

#[test]
fn test_cc_hk_011_string_timeout() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo test", "timeout": "thirty" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_011: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-011")
        .collect();

    assert_eq!(cc_hk_011.len(), 1);
    assert_eq!(cc_hk_011[0].level, DiagnosticLevel::Error);
}

#[test]
fn test_cc_hk_011_null_timeout() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo test", "timeout": null }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_011: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-011")
        .collect();

    assert_eq!(cc_hk_011.len(), 1);
    assert_eq!(cc_hk_011[0].level, DiagnosticLevel::Error);
}

#[test]
fn test_cc_hk_011_positive_timeout_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test", "timeout": 30 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_011: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-011")
        .collect();

    assert_eq!(cc_hk_011.len(), 0);
}

#[test]
fn test_cc_hk_011_multiple_invalid_timeouts() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "*",
                        "hooks": [
                            { "type": "command", "command": "echo 'negative'", "timeout": -5 },
                            { "type": "command", "command": "echo 'zero'", "timeout": 0 },
                            { "type": "command", "command": "echo 'valid'", "timeout": 30 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_011: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-011")
        .collect();

    assert_eq!(cc_hk_011.len(), 2);
}

#[test]
fn test_cc_hk_011_missing_timeout_not_triggered() {
    // CC-HK-011 should NOT trigger when timeout is missing (that's CC-HK-010's job)
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_011: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-011")
        .collect();

    assert_eq!(cc_hk_011.len(), 0);
}

#[test]
fn test_fixture_invalid_timeout() {
    let content =
        include_str!("../../../../../tests/fixtures/invalid/hooks/invalid-timeout/settings.json");
    let diagnostics = validate(content);
    let cc_hk_011: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-011")
        .collect();
    // zero (0) appears twice - 2 invalid timeouts
    assert_eq!(cc_hk_011.len(), 2);
}

// ===== Config Wiring Tests =====

#[test]
fn test_config_disabled_hooks_category_returns_empty() {
    let mut config = LintConfig::default();
    config.rules_mut().hooks = false;

    let content = r#"{
            "hooks": {
                "InvalidEvent": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo test" }
                        ]
                    }
                ]
            }
        }"#;

    let validator = HooksValidator;
    let diagnostics = validator.validate(Path::new("settings.json"), content, &config);

    // CC-HK-001 should not fire when hooks category is disabled
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();
    assert_eq!(cc_hk_001.len(), 0);
}

#[test]
fn test_config_disabled_specific_hook_rule() {
    let mut config = LintConfig::default();
    config.rules_mut().disabled_rules = vec!["CC-HK-006".to_string()];

    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command" }
                        ]
                    }
                ]
            }
        }"#;

    let validator = HooksValidator;
    let diagnostics = validator.validate(Path::new("settings.json"), content, &config);

    // CC-HK-006 should not fire when specifically disabled
    let cc_hk_006: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-006")
        .collect();
    assert_eq!(cc_hk_006.len(), 0);
}

#[test]
fn test_config_cursor_target_disables_hooks_rules() {
    use crate::config::TargetTool;

    let mut config = LintConfig::default();
    config.set_target(TargetTool::Cursor);

    let content = r#"{
            "hooks": {
                "InvalidEvent": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo test" }
                        ]
                    }
                ]
            }
        }"#;

    let validator = HooksValidator;
    let diagnostics = validator.validate(Path::new("settings.json"), content, &config);

    // CC-HK-* rules should not fire for Cursor target
    let hook_rules: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule.starts_with("CC-HK-"))
        .collect();
    assert_eq!(hook_rules.len(), 0);
}

#[test]
fn test_config_dangerous_pattern_disabled() {
    let mut config = LintConfig::default();
    config.rules_mut().disabled_rules = vec!["CC-HK-009".to_string()];

    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "command", "command": "rm -rf /" }
                        ]
                    }
                ]
            }
        }"#;

    let validator = HooksValidator;
    let diagnostics = validator.validate(Path::new("settings.json"), content, &config);

    // CC-HK-009 should not fire when specifically disabled
    let cc_hk_009: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-009")
        .collect();
    assert_eq!(cc_hk_009.len(), 0);
}

// ===== Version-Aware CC-HK-010 Tests =====

#[test]
fn test_cc_hk_010_assumption_when_version_not_pinned() {
    // Default config has no version pinned
    let config = LintConfig::default();
    assert!(!config.is_claude_code_version_pinned());

    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test" }
                        ]
                    }
                ]
            }
        }"#;

    let validator = HooksValidator;
    let diagnostics = validator.validate(Path::new("settings.json"), content, &config);

    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();

    assert_eq!(cc_hk_010.len(), 1);
    // Should have an assumption note when version not pinned
    assert!(cc_hk_010[0].assumption.is_some());
    let assumption = cc_hk_010[0].assumption.as_ref().unwrap();
    assert!(assumption.contains("Assumes Claude Code default timeout behavior"));
    assert!(assumption.contains("[tool_versions]"));
}

#[test]
fn test_cc_hk_010_no_assumption_when_version_pinned() {
    let mut config = LintConfig::default();
    config.tool_versions_mut().claude_code = Some("1.0.0".to_string());
    assert!(config.is_claude_code_version_pinned());

    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test" }
                        ]
                    }
                ]
            }
        }"#;

    let validator = HooksValidator;
    let diagnostics = validator.validate(Path::new("settings.json"), content, &config);

    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();

    assert_eq!(cc_hk_010.len(), 1);
    // Should NOT have an assumption note when version is pinned
    assert!(cc_hk_010[0].assumption.is_none());
}

#[test]
fn test_cc_hk_010_prompt_assumption_when_version_not_pinned() {
    let config = LintConfig::default();

    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "Summarize session" }
                        ]
                    }
                ]
            }
        }"#;

    let validator = HooksValidator;
    let diagnostics = validator.validate(Path::new("settings.json"), content, &config);

    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();

    assert_eq!(cc_hk_010.len(), 1);
    assert!(cc_hk_010[0].assumption.is_some());
}

#[test]
fn test_cc_hk_010_exceeds_default_assumption_when_version_not_pinned() {
    let config = LintConfig::default();

    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test", "timeout": 700 }
                        ]
                    }
                ]
            }
        }"#;

    let validator = HooksValidator;
    let diagnostics = validator.validate(Path::new("settings.json"), content, &config);

    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();

    assert_eq!(cc_hk_010.len(), 1);
    // Warning about exceeding default should also have assumption when unpinned
    assert!(cc_hk_010[0].assumption.is_some());
}

// ===== CC-HK-012: Hooks Parse Error =====

#[test]
fn test_cc_hk_012_invalid_json_syntax() {
    let content = r#"{ "hooks": { invalid syntax } }"#;

    let diagnostics = validate(content);

    let parse_errors: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-012")
        .collect();
    assert_eq!(parse_errors.len(), 1);
    assert!(parse_errors[0].message.contains("Failed to parse"));
}

#[test]
fn test_cc_hk_012_truncated_json() {
    let content = r#"{"hooks": {"Stop": [{"hooks": [{"type":"command""#;

    let diagnostics = validate(content);

    let parse_errors: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-012")
        .collect();
    assert_eq!(parse_errors.len(), 1);
}

#[test]
fn test_cc_hk_012_empty_file() {
    let content = "";

    let diagnostics = validate(content);

    let parse_errors: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-012")
        .collect();
    assert_eq!(parse_errors.len(), 1);
}

#[test]
fn test_cc_hk_012_valid_json_no_error() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo done" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);

    let parse_errors: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-012")
        .collect();
    assert_eq!(parse_errors.len(), 0);
}

#[test]
fn test_cc_hk_012_disabled() {
    let mut config = LintConfig::default();
    config.rules_mut().disabled_rules = vec!["CC-HK-012".to_string()];

    let content = r#"{ invalid json }"#;
    let validator = HooksValidator;
    let diagnostics = validator.validate(Path::new("settings.json"), content, &config);

    assert!(!diagnostics.iter().any(|d| d.rule == "CC-HK-012"));
}

#[test]
fn test_cc_hk_012_missing_required_field_hooks_key() {
    // Valid JSON but missing the "hooks" key entirely - should NOT be CC-HK-012
    let content = r#"{"model": "sonnet"}"#;

    let diagnostics = validate(content);

    // This is valid JSON, just doesn't have hooks - no parse error
    assert!(!diagnostics.iter().any(|d| d.rule == "CC-HK-012"));
}

#[test]
fn test_cc_hk_012_null_value() {
    let content = r#"{"hooks": null}"#;

    let diagnostics = validate(content);

    // null for hooks triggers parse error since HooksSchema expects an object
    let parse_errors: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-012")
        .collect();
    assert_eq!(parse_errors.len(), 1);
}

#[test]
fn test_cc_hk_012_array_instead_of_object() {
    let content = r#"["hooks", "array"]"#;

    let diagnostics = validate(content);

    // This is valid JSON but wrong structure - should trigger parse error
    // because SettingsSchema expects an object
    let parse_errors: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-012")
        .collect();
    assert_eq!(parse_errors.len(), 1);
}

// ===== Additional CC-HK edge case tests =====

#[test]
fn test_cc_hk_001_all_valid_events() {
    // Tool events that require matcher
    let tool_events = ["PreToolUse", "PostToolUse", "PermissionRequest"];
    // Non-tool events that don't require matcher
    let non_tool_events = [
        "Stop",
        "SubagentStop",
        "SessionStart",
        "TeammateIdle",
        "TaskCompleted",
    ];

    // Test tool events WITH matcher (should be valid)
    for event in tool_events {
        let content = format!(
            r#"{{
                    "hooks": {{
                        "{}": [
                            {{
                                "matcher": "Bash",
                                "hooks": [{{ "type": "command", "command": "echo test" }}]
                            }}
                        ]
                    }}
                }}"#,
            event
        );

        let diagnostics = validate(&content);
        let hk_001: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CC-HK-001")
            .collect();
        assert!(
            hk_001.is_empty(),
            "Tool event '{}' with matcher should be valid",
            event
        );
    }

    // Test non-tool events WITHOUT matcher (should be valid)
    for event in non_tool_events {
        let content = format!(
            r#"{{
                    "hooks": {{
                        "{}": [
                            {{
                                "hooks": [{{ "type": "command", "command": "echo test" }}]
                            }}
                        ]
                    }}
                }}"#,
            event
        );

        let diagnostics = validate(&content);
        let hk_001: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CC-HK-001")
            .collect();
        assert!(
            hk_001.is_empty(),
            "Non-tool event '{}' without matcher should be valid",
            event
        );
    }
}

#[test]
fn test_cc_hk_003_all_tool_events_hint_matcher() {
    // Must match HooksSchema::TOOL_EVENTS constant
    let tool_events = HooksSchema::TOOL_EVENTS;

    for event in tool_events {
        let content = format!(
            r#"{{
                    "hooks": {{
                        "{}": [
                            {{
                                "hooks": [{{ "type": "command", "command": "echo test" }}]
                            }}
                        ]
                    }}
                }}"#,
            event
        );

        let diagnostics = validate(&content);
        let hk_003: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CC-HK-003")
            .collect();
        assert_eq!(
            hk_003.len(),
            1,
            "Event '{}' without matcher should get CC-HK-003 hint",
            event
        );
        assert_eq!(
            hk_003[0].level,
            DiagnosticLevel::Info,
            "Event '{}' CC-HK-003 should be Info level",
            event
        );
    }
}

#[test]
fn test_cc_hk_004_non_tool_events_reject_matcher() {
    // Stop and UserPromptSubmit are handled by CC-HK-018 instead
    // SubagentStop, SessionStart, etc. now support matchers via MATCHER_EVENTS
    let non_tool_events = ["TeammateIdle", "TaskCompleted"];

    for event in non_tool_events {
        let content = format!(
            r#"{{
                    "hooks": {{
                        "{}": [
                            {{
                                "matcher": "Bash",
                                "hooks": [{{ "type": "command", "command": "echo test" }}]
                            }}
                        ]
                    }}
                }}"#,
            event
        );

        let diagnostics = validate(&content);
        let hk_004: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CC-HK-004")
            .collect();
        assert_eq!(
            hk_004.len(),
            1,
            "Event '{}' should reject matcher but didn't get CC-HK-004",
            event
        );
    }
}

#[test]
fn test_cc_hk_002_prompt_allowed_events() {
    // Must match HooksSchema::PROMPT_EVENTS constant
    let prompt_allowed = HooksSchema::PROMPT_EVENTS;
    assert_eq!(
        prompt_allowed.len(),
        8,
        "Expected exactly 8 prompt-allowed events"
    );

    for event in prompt_allowed {
        let content = format!(
            r#"{{
                    "hooks": {{
                        "{}": [
                            {{
                                "hooks": [{{ "type": "prompt", "prompt": "Summarize" }}]
                            }}
                        ]
                    }}
                }}"#,
            event
        );

        let diagnostics = validate(&content);
        let hk_002: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CC-HK-002")
            .collect();
        assert!(
            hk_002.is_empty(),
            "Prompt on '{}' should be valid but got CC-HK-002",
            event
        );
    }
}

#[test]
fn test_cc_hk_002_prompt_on_task_completed_ok() {
    let content = r#"{
            "hooks": {
                "TaskCompleted": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "verify task completion" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_002: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-002")
        .collect();

    assert_eq!(cc_hk_002.len(), 0);
}

#[test]
fn test_cc_hk_002_prompt_disallowed_events() {
    let prompt_disallowed = [
        "SessionStart",
        "SessionEnd",
        "Notification",
        "SubagentStart",
        "PreCompact",
        "Setup",
        "TeammateIdle",
    ];

    for event in prompt_disallowed {
        let content = format!(
            r#"{{
                    "hooks": {{
                        "{}": [
                            {{
                                "hooks": [{{ "type": "prompt", "prompt": "Test" }}]
                            }}
                        ]
                    }}
                }}"#,
            event
        );

        let diagnostics = validate(&content);
        let hk_002: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CC-HK-002")
            .collect();
        assert_eq!(
            hk_002.len(),
            1,
            "Prompt on '{}' should be invalid but didn't get CC-HK-002",
            event
        );
    }
}

#[test]
fn test_cc_hk_002_agent_disallowed_events() {
    let agent_disallowed = [
        "Setup",
        "SessionStart",
        "SessionEnd",
        "Notification",
        "SubagentStart",
        "PreCompact",
        "TeammateIdle",
    ];

    for event in agent_disallowed {
        let content = format!(
            r#"{{
                    "hooks": {{
                        "{}": [
                            {{
                                "hooks": [{{ "type": "agent", "prompt": "Test $ARGUMENTS" }}]
                            }}
                        ]
                    }}
                }}"#,
            event
        );

        let diagnostics = validate(&content);
        let hk_002: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CC-HK-002")
            .collect();
        assert_eq!(
            hk_002.len(),
            1,
            "Agent on '{}' should be invalid but didn't get CC-HK-002",
            event
        );
    }
}

// ===== CC-HK-013 Tests: Async on Non-Command Hook =====

#[test]
fn test_cc_hk_013_async_on_prompt_hook() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "Summarize $ARGUMENTS", "async": true }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_013: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-013")
        .collect();

    assert_eq!(cc_hk_013.len(), 1);
    assert_eq!(cc_hk_013[0].level, DiagnosticLevel::Error);
    assert!(cc_hk_013[0].message.contains("async"));
    assert!(cc_hk_013[0].message.contains("prompt"));
}

#[test]
fn test_cc_hk_013_async_on_agent_hook() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "agent", "prompt": "Review $ARGUMENTS", "async": true }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_013: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-013")
        .collect();

    assert_eq!(cc_hk_013.len(), 1);
    assert!(cc_hk_013[0].message.contains("agent"));
}

#[test]
fn test_cc_hk_013_async_on_command_hook_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test", "async": true }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_013: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-013")
        .collect();

    assert_eq!(cc_hk_013.len(), 0);
}

#[test]
fn test_fixture_async_on_non_command() {
    let content = include_str!(
        "../../../../../tests/fixtures/invalid/hooks/async-on-non-command/settings.json"
    );
    let diagnostics = validate(content);
    let cc_hk_013: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-013")
        .collect();
    // prompt and agent hooks both have async
    assert_eq!(cc_hk_013.len(), 2);
}

// ===== CC-HK-014 Tests: Once Outside Skill/Agent Frontmatter =====

#[test]
fn test_cc_hk_014_once_in_settings() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo done", "once": true }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_014: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-014")
        .collect();

    assert_eq!(cc_hk_014.len(), 1);
    assert_eq!(cc_hk_014[0].level, DiagnosticLevel::Warning);
    assert!(cc_hk_014[0].message.contains("once"));
}

#[test]
fn test_cc_hk_014_no_once_ok() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo done" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_014: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-014")
        .collect();

    assert_eq!(cc_hk_014.len(), 0);
}

#[test]
fn test_fixture_once_in_settings() {
    let content =
        include_str!("../../../../../tests/fixtures/invalid/hooks/once-in-settings/settings.json");
    let diagnostics = validate(content);
    let cc_hk_014: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-014")
        .collect();
    // Two hooks with once field
    assert_eq!(cc_hk_014.len(), 2);
}

// ===== CC-HK-015 Tests: Model on Command Hook =====

#[test]
fn test_cc_hk_015_model_on_command() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test", "model": "claude-sonnet-4-5-20250929" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_015: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-015")
        .collect();

    assert_eq!(cc_hk_015.len(), 1);
    assert_eq!(cc_hk_015[0].level, DiagnosticLevel::Warning);
    assert!(cc_hk_015[0].message.contains("model"));
}

#[test]
fn test_cc_hk_015_model_on_prompt_ok() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "Summarize $ARGUMENTS", "model": "claude-sonnet-4-5-20250929" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_015: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-015")
        .collect();

    assert_eq!(cc_hk_015.len(), 0);
}

#[test]
fn test_cc_hk_015_model_on_agent_ok() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "agent", "prompt": "Review $ARGUMENTS", "model": "claude-sonnet-4-5-20250929" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_015: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-015")
        .collect();

    assert_eq!(cc_hk_015.len(), 0);
}

#[test]
fn test_cc_hk_015_no_model_on_command_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_015: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-015")
        .collect();

    assert_eq!(cc_hk_015.len(), 0);
}

#[test]
fn test_fixture_model_on_command() {
    let content =
        include_str!("../../../../../tests/fixtures/invalid/hooks/model-on-command/settings.json");
    let diagnostics = validate(content);
    let cc_hk_015: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-015")
        .collect();
    assert_eq!(cc_hk_015.len(), 1);
}

// ===== CC-HK-016 Tests: Validate Hook Type Agent =====

#[test]
fn test_cc_hk_016_agent_type_valid() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "agent", "prompt": "Review $ARGUMENTS" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_016: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-016")
        .collect();

    assert_eq!(cc_hk_016.len(), 0);
}

#[test]
fn test_cc_hk_016_mcp_tool_type_valid() {
    // Claude Code v2.1.118 added `type: "mcp_tool"` so hooks can invoke
    // MCP tools directly. Without the scaffolding added in that fix,
    // agnix would false-positive CC-HK-016 (unknown hook type) AND
    // CC-HK-012 (typed-schema mismatch).
    //
    // Since #804 we also know the full schema from the upstream docs
    // (code.claude.com/docs/en/hooks#mcp-tool-hook-fields) and validate
    // that `server` + `tool` are both required strings. So the fixture
    // here now includes both.
    let content = r#"{
            "hooks": {
                "PostToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "mcp_tool", "server": "my_server", "tool": "do_thing" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    // Strict assertion: NO diagnostics should fire on a valid mcp_tool hook.
    // Covers CC-HK-016 (string allow-list), CC-HK-012 (typed Hook enum
    // schema match), CC-HK-026 (server required), and CC-HK-027 (tool
    // required).
    assert!(
        diagnostics.is_empty(),
        "valid mcp_tool hook should produce zero diagnostics, got: {:?}",
        diagnostics
    );
}

#[test]
fn test_cc_hk_016_unknown_type() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "webhook", "url": "https://example.com" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_016: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-016")
        .collect();

    assert_eq!(cc_hk_016.len(), 1);
    assert_eq!(cc_hk_016[0].level, DiagnosticLevel::Error);
    assert!(cc_hk_016[0].message.contains("webhook"));
}

#[test]
fn test_cc_hk_016_command_and_prompt_still_valid() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo done" },
                            { "type": "prompt", "prompt": "Summarize $ARGUMENTS" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_016: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-016")
        .collect();

    assert_eq!(cc_hk_016.len(), 0);
}

#[test]
fn test_fixture_unknown_hook_type() {
    let content =
        include_str!("../../../../../tests/fixtures/invalid/hooks/unknown-hook-type/settings.json");
    let diagnostics = validate(content);
    let cc_hk_016: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-016")
        .collect();
    assert_eq!(cc_hk_016.len(), 1);
}

#[test]
fn test_cc_hk_016_non_string_type() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": 123, "command": "echo bad" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_016: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-016")
        .collect();

    assert_eq!(
        cc_hk_016.len(),
        1,
        "Non-string type value should trigger CC-HK-016"
    );
}

// ===== CC-HK-017 Tests: Prompt Hook Missing $ARGUMENTS =====

#[test]
fn test_cc_hk_017_prompt_missing_arguments() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "Summarize what happened in this session" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_017: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-017")
        .collect();

    assert_eq!(cc_hk_017.len(), 1);
    assert_eq!(cc_hk_017[0].level, DiagnosticLevel::Warning);
    assert!(cc_hk_017[0].message.contains("$ARGUMENTS"));
}

#[test]
fn test_cc_hk_017_prompt_with_arguments_ok() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "prompt", "prompt": "Summarize: $ARGUMENTS" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_017: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-017")
        .collect();

    assert_eq!(cc_hk_017.len(), 0);
}

#[test]
fn test_cc_hk_017_agent_missing_arguments() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "agent", "prompt": "Review the session" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_017: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-017")
        .collect();

    assert_eq!(cc_hk_017.len(), 1);
}

#[test]
fn test_cc_hk_017_agent_with_arguments_ok() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "agent", "prompt": "Review: $ARGUMENTS" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_017: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-017")
        .collect();

    assert_eq!(cc_hk_017.len(), 0);
}

#[test]
fn test_cc_hk_017_no_prompt_field_no_trigger() {
    // Missing prompt field should not trigger CC-HK-017 (that's CC-HK-007's job)
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "prompt" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_017: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-017")
        .collect();

    assert_eq!(cc_hk_017.len(), 0);
}

#[test]
fn test_fixture_prompt_missing_arguments() {
    let content = include_str!(
        "../../../../../tests/fixtures/invalid/hooks/prompt-missing-arguments/settings.json"
    );
    let diagnostics = validate(content);
    let cc_hk_017: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-017")
        .collect();
    // First prompt lacks $ARGUMENTS, second has it
    assert_eq!(cc_hk_017.len(), 1);
}

// ===== CC-HK-018 Tests: Matcher on UserPromptSubmit/Stop =====

#[test]
fn test_cc_hk_018_matcher_on_user_prompt_submit() {
    let content = r#"{
            "hooks": {
                "UserPromptSubmit": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo submitted" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_018: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-018")
        .collect();

    assert_eq!(cc_hk_018.len(), 1);
    assert_eq!(cc_hk_018[0].level, DiagnosticLevel::Info);
    assert!(cc_hk_018[0].message.contains("silently ignored"));
    assert!(cc_hk_018[0].message.contains("UserPromptSubmit"));
}

#[test]
fn test_cc_hk_018_matcher_on_stop() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo done" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_018: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-018")
        .collect();

    assert_eq!(cc_hk_018.len(), 1);
    assert!(cc_hk_018[0].message.contains("Stop"));
}

#[test]
fn test_cc_hk_018_matcher_on_pretooluse_ok() {
    // PreToolUse is a tool event - matcher is expected
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo test" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_018: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-018")
        .collect();

    assert_eq!(cc_hk_018.len(), 0);
}

#[test]
fn test_cc_hk_018_no_matcher_ok() {
    let content = r#"{
            "hooks": {
                "UserPromptSubmit": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo submitted" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_018: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-018")
        .collect();

    assert_eq!(cc_hk_018.len(), 0);
}

#[test]
fn test_fixture_matcher_on_ignored_event() {
    let content = include_str!(
        "../../../../../tests/fixtures/invalid/hooks/matcher-on-ignored-event/settings.json"
    );
    let diagnostics = validate(content);
    let cc_hk_018: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-018")
        .collect();
    // UserPromptSubmit and Stop both have matchers
    assert_eq!(cc_hk_018.len(), 2);
}

// ===== CC-HK-016: Agent Hook Integration Tests =====

#[test]
fn test_agent_hook_on_stop_valid() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "agent", "prompt": "Review session $ARGUMENTS", "timeout": 30 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    // Should not trigger CC-HK-016 (valid type)
    assert!(!diagnostics.iter().any(|d| d.rule == "CC-HK-016"));
    // Should not trigger CC-HK-002 (agent allowed on Stop)
    assert!(!diagnostics.iter().any(|d| d.rule == "CC-HK-002"));
}

#[test]
fn test_agent_hook_on_pretooluse_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "agent", "prompt": "Review $ARGUMENTS", "timeout": 30 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    // Agent hooks are allowed on PreToolUse
    assert!(!diagnostics.iter().any(|d| d.rule == "CC-HK-002"));
}

#[test]
fn test_agent_hook_missing_prompt() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "agent" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_007: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-007")
        .collect();
    assert_eq!(cc_hk_007.len(), 1);
}

#[test]
fn test_agent_hook_timeout_policy() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "agent", "prompt": "Review $ARGUMENTS" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_010: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-010")
        .collect();
    // Agent hooks should get timeout warnings like prompt hooks
    assert_eq!(cc_hk_010.len(), 1);
}

// ===== CC-HK-013 Auto-Fix =====

#[test]
fn test_cc_hk_013_has_autofix_when_unique() {
    let content = r#"{
        "hooks": {
            "Stop": [
                {
                    "hooks": [
                        {
                            "type": "prompt",
                            "async": true,
                            "prompt": "hello"
                        }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let cc_hk_013: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-013")
        .collect();
    assert_eq!(cc_hk_013.len(), 1);
    assert!(
        cc_hk_013[0].has_fixes(),
        "CC-HK-013 should have auto-fix when async is unique"
    );
    assert!(cc_hk_013[0].fixes[0].safe, "CC-HK-013 fix should be safe");
    assert!(
        cc_hk_013[0].fixes[0].replacement.is_empty(),
        "CC-HK-013 fix should be a deletion"
    );
}

#[test]
fn test_cc_hk_013_no_autofix_when_duplicate() {
    // Two hooks with async - no fix due to uniqueness guard
    let content = r#"{
        "hooks": {
            "Stop": [
                {
                    "hooks": [
                        {
                            "type": "prompt",
                            "async": true,
                            "prompt": "hello"
                        },
                        {
                            "type": "prompt",
                            "async": false,
                            "prompt": "world"
                        }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let cc_hk_013: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-013")
        .collect();
    assert_eq!(cc_hk_013.len(), 2);
    // At least one should lack a fix due to duplicate "async" fields
    let no_fix = cc_hk_013.iter().any(|d| !d.has_fixes());
    assert!(
        no_fix,
        "CC-HK-013 should not have auto-fix when async appears multiple times"
    );
}

// ===== CC-HK-015 Auto-Fix =====

#[test]
fn test_cc_hk_015_has_autofix_when_unique() {
    let content = r#"{
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "Bash",
                    "hooks": [
                        {
                            "type": "command",
                            "command": "echo hi",
                            "model": "sonnet"
                        }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let cc_hk_015: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-015")
        .collect();
    assert_eq!(cc_hk_015.len(), 1);
    assert!(
        cc_hk_015[0].has_fixes(),
        "CC-HK-015 should have auto-fix when model is unique"
    );
    assert!(cc_hk_015[0].fixes[0].safe, "CC-HK-015 fix should be safe");
    assert!(
        cc_hk_015[0].fixes[0].replacement.is_empty(),
        "CC-HK-015 fix should be a deletion"
    );
}

#[test]
fn test_cc_hk_015_no_autofix_when_duplicate() {
    let content = r#"{
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "Bash",
                    "hooks": [
                        {
                            "type": "command",
                            "command": "echo hi",
                            "model": "sonnet"
                        },
                        {
                            "type": "command",
                            "command": "echo bye",
                            "model": "opus"
                        }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let cc_hk_015: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-015")
        .collect();
    assert_eq!(cc_hk_015.len(), 2);
    let no_fix = cc_hk_015.iter().any(|d| !d.has_fixes());
    assert!(
        no_fix,
        "CC-HK-015 should not have auto-fix when model appears multiple times"
    );
}

// ===== CC-HK-018 Auto-Fix =====

#[test]
fn test_cc_hk_018_has_autofix_when_unique() {
    let content = r#"{
        "hooks": {
            "UserPromptSubmit": [
                {
                    "matcher": "test-tool",
                    "hooks": [
                        { "type": "command", "command": "echo hi" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let cc_hk_018: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-018")
        .collect();
    assert_eq!(cc_hk_018.len(), 1);
    assert!(
        cc_hk_018[0].has_fixes(),
        "CC-HK-018 should have auto-fix when matcher is unique"
    );
    assert!(cc_hk_018[0].fixes[0].safe, "CC-HK-018 fix should be safe");
    assert!(
        cc_hk_018[0].fixes[0].replacement.is_empty(),
        "CC-HK-018 fix should be a deletion"
    );
}

#[test]
fn test_cc_hk_018_no_autofix_when_duplicate_matcher() {
    // Two matchers with same value - no fix
    let content = r#"{
        "hooks": {
            "UserPromptSubmit": [
                {
                    "matcher": "test-tool",
                    "hooks": [
                        { "type": "command", "command": "echo hi" }
                    ]
                },
                {
                    "matcher": "test-tool",
                    "hooks": [
                        { "type": "command", "command": "echo bye" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let cc_hk_018: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-018")
        .collect();
    assert_eq!(cc_hk_018.len(), 2);
    // Both should lack fixes because matcher "test-tool" appears twice
    assert!(
        cc_hk_018.iter().all(|d| !d.has_fixes()),
        "CC-HK-018 should not have auto-fix when matcher is duplicated"
    );
}

// ===== CC-HK-016 Auto-Fix =====

#[test]
fn test_cc_hk_016_autofix_case_insensitive() {
    // "Command" is close to "command" - should produce autofix
    let content = r#"{
        "hooks": {
            "Stop": [
                {
                    "hooks": [
                        { "type": "Command", "command": "echo hi" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let cc_hk_016: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-016")
        .collect();
    assert_eq!(cc_hk_016.len(), 1);
    assert!(
        cc_hk_016[0].has_fixes(),
        "CC-HK-016 should have auto-fix for case-insensitive match"
    );
    assert!(
        !cc_hk_016[0].fixes[0].safe,
        "CC-HK-016 fix should be unsafe"
    );
    assert_eq!(
        cc_hk_016[0].fixes[0].replacement, "command",
        "CC-HK-016 fix should replace with 'command'"
    );
}

#[test]
fn test_cc_hk_016_no_autofix_nonsense() {
    // "xyzzy" is too far from any valid type - no autofix
    let content = r#"{
        "hooks": {
            "Stop": [
                {
                    "hooks": [
                        { "type": "xyzzy", "command": "echo hi" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let cc_hk_016: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-016")
        .collect();
    assert_eq!(cc_hk_016.len(), 1);
    assert!(
        !cc_hk_016[0].has_fixes(),
        "CC-HK-016 should not have auto-fix for nonsense value"
    );
}

#[test]
fn test_cc_hk_016_autofix_targets_correct_bytes() {
    let content = r#"{
        "hooks": {
            "Stop": [
                {
                    "hooks": [
                        { "type": "Prompt", "prompt": "hello" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let cc_hk_016: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-016")
        .collect();
    assert_eq!(cc_hk_016.len(), 1);
    assert!(cc_hk_016[0].has_fixes());
    let fix = &cc_hk_016[0].fixes[0];
    assert_eq!(fix.replacement, "prompt");
    // The bytes targeted should correspond to "Prompt" in the content
    let targeted = &content[fix.start_byte..fix.end_byte];
    assert_eq!(
        targeted, "Prompt",
        "Fix should target the exact value bytes"
    );
}

#[test]
fn test_cc_hk_016_no_autofix_when_duplicate_type() {
    // Two hooks both have the same invalid type value "Command" (case mismatch).
    // The uniqueness guard should prevent autofix since "type": "Command"
    // appears more than once.
    let content = r#"{
        "hooks": {
            "Stop": [
                {
                    "hooks": [
                        { "type": "Command", "command": "echo first" },
                        { "type": "Command", "command": "echo second" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let cc_hk_016: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-016")
        .collect();
    assert_eq!(cc_hk_016.len(), 2, "Should flag both invalid types");
    for diag in &cc_hk_016 {
        assert!(
            !diag.has_fixes(),
            "CC-HK-016 should not have auto-fix when type value appears multiple times"
        );
    }
}

#[test]
fn test_cc_hk_016_no_autofix_for_non_string() {
    // Non-string type value should not produce autofix
    let content = r#"{
        "hooks": {
            "Stop": [
                {
                    "hooks": [
                        { "type": 123, "command": "echo bad" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let cc_hk_016: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-016")
        .collect();
    assert_eq!(cc_hk_016.len(), 1);
    assert!(
        !cc_hk_016[0].has_fixes(),
        "CC-HK-016 should not have auto-fix for non-string type values"
    );
}

// ===== CC-HK-012 suggestion tests =====

#[test]
fn test_cc_hk_012_has_suggestion_on_invalid_json() {
    let content = r#"{ "hooks": { invalid syntax } }"#;
    let diagnostics = validate(content);

    let parse_errors: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-012")
        .collect();
    assert_eq!(parse_errors.len(), 1);
    assert!(
        parse_errors[0].suggestion.is_some(),
        "CC-HK-012 should have a suggestion"
    );
    assert!(
        parse_errors[0]
            .suggestion
            .as_ref()
            .unwrap()
            .contains("Validate JSON syntax"),
        "CC-HK-012 suggestion should mention JSON syntax"
    );
}

#[test]
fn test_cc_hk_012_has_suggestion_on_schema_mismatch() {
    // Valid JSON but wrong structure triggers the second CC-HK-012 call site
    let content = r#"{"hooks": null}"#;
    let diagnostics = validate(content);

    let parse_errors: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-012")
        .collect();
    assert_eq!(parse_errors.len(), 1);
    assert!(
        parse_errors[0].suggestion.is_some(),
        "CC-HK-012 schema mismatch should also have a suggestion"
    );
}

// ===== CC-HK-019 Tests: Deprecated Setup Event =====

#[test]
fn test_cc_hk_019_setup_triggers_deprecation_warning() {
    let content = r#"{
            "hooks": {
                "Setup": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo setup", "timeout": 30 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_019: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-019")
        .collect();

    assert_eq!(cc_hk_019.len(), 1);
    assert_eq!(cc_hk_019[0].level, DiagnosticLevel::Warning);
    assert!(
        cc_hk_019[0].message.contains("Deprecated"),
        "Should mention deprecation, got: {}",
        cc_hk_019[0].message
    );
    assert!(
        cc_hk_019[0].message.contains("Setup"),
        "Should mention Setup event, got: {}",
        cc_hk_019[0].message
    );
    assert!(
        cc_hk_019[0].message.contains("SessionStart"),
        "Should suggest SessionStart replacement, got: {}",
        cc_hk_019[0].message
    );
}

#[test]
fn test_cc_hk_019_session_start_no_warning() {
    let content = r#"{
            "hooks": {
                "SessionStart": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo start", "timeout": 30 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_019: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-019")
        .collect();

    assert_eq!(
        cc_hk_019.len(),
        0,
        "SessionStart should NOT trigger CC-HK-019"
    );
}

#[test]
fn test_cc_hk_019_has_autofix() {
    let content = r#"{
  "hooks": {
    "Setup": [
      {
        "hooks": [
          { "type": "command", "command": "echo setup", "timeout": 30 }
        ]
      }
    ]
  }
}"#;

    let diagnostics = validate(content);
    let cc_hk_019: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-019")
        .collect();

    assert_eq!(cc_hk_019.len(), 1);
    assert!(cc_hk_019[0].has_fixes(), "CC-HK-019 should have auto-fix");
    let fix = &cc_hk_019[0].fixes[0];
    assert!(
        fix.replacement.contains("SessionStart"),
        "Fix should replace with SessionStart, got: {}",
        fix.replacement
    );
}

#[test]
fn test_cc_hk_019_fix_is_unsafe() {
    let content = r#"{
  "hooks": {
    "Setup": [
      {
        "hooks": [
          { "type": "command", "command": "echo setup", "timeout": 30 }
        ]
      }
    ]
  }
}"#;

    let diagnostics = validate(content);
    let cc_hk_019: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-019")
        .collect();

    assert_eq!(cc_hk_019.len(), 1);
    assert!(cc_hk_019[0].has_fixes(), "CC-HK-019 should have auto-fix");
    let fix = &cc_hk_019[0].fixes[0];
    assert!(!fix.safe, "CC-HK-019 fix should be unsafe (safe=false)");
}

#[test]
fn test_cc_hk_019_can_be_disabled() {
    let content = r#"{
            "hooks": {
                "Setup": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo setup", "timeout": 30 }
                        ]
                    }
                ]
            }
        }"#;

    let config = LintConfig::builder()
        .disable_rule("CC-HK-019")
        .build()
        .unwrap();
    let validator = HooksValidator;
    let diagnostics = validator.validate(Path::new("settings.json"), content, &config);
    let cc_hk_019: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-019")
        .collect();

    assert_eq!(
        cc_hk_019.len(),
        0,
        "CC-HK-019 should not fire when disabled"
    );
}

#[test]
fn test_cc_hk_019_fixture_deprecated_setup_event() {
    let content = include_str!(
        "../../../../../tests/fixtures/invalid/hooks/deprecated-setup-event/settings.json"
    );
    let diagnostics = validate(content);
    let cc_hk_019: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-019")
        .collect();

    assert_eq!(cc_hk_019.len(), 1, "Fixture should trigger CC-HK-019");
    assert_eq!(cc_hk_019[0].level, DiagnosticLevel::Warning);
}

#[test]
fn test_cc_hk_019_setup_does_not_trigger_cc_hk_001() {
    // Setup is in VALID_EVENTS, so CC-HK-001 should NOT fire
    let content = r#"{
            "hooks": {
                "Setup": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo setup", "timeout": 30 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_001: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-001")
        .collect();

    assert_eq!(
        cc_hk_001.len(),
        0,
        "Setup is a valid event - CC-HK-001 should NOT fire"
    );
}

#[test]
fn test_cc_hk_019_case_variants_trigger_cc_hk_001_not_019() {
    // "setup" (lowercase) and "SetUp" (wrong case) are NOT deprecated - they are invalid.
    // They should trigger CC-HK-001 (invalid event), not CC-HK-019 (deprecated event).
    for variant in &["setup", "SetUp", "SETUP", "Setups"] {
        let content = format!(
            r#"{{
            "hooks": {{
                "{}": [
                    {{
                        "hooks": [
                            {{ "type": "command", "command": "echo test", "timeout": 30 }}
                        ]
                    }}
                ]
            }}
        }}"#,
            variant
        );

        let diagnostics = validate(&content);
        let cc_hk_001: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CC-HK-001")
            .collect();
        let cc_hk_019: Vec<_> = diagnostics
            .iter()
            .filter(|d| d.rule == "CC-HK-019")
            .collect();

        assert_eq!(
            cc_hk_001.len(),
            1,
            "'{}' should trigger CC-HK-001 (invalid event)",
            variant
        );
        assert_eq!(
            cc_hk_019.len(),
            0,
            "'{}' should NOT trigger CC-HK-019 (not exact match for deprecated)",
            variant
        );
    }
}

#[test]
fn test_cc_hk_019_suggestion_field() {
    let content = r#"{
  "hooks": {
    "Setup": [
      {
        "hooks": [
          { "type": "command", "command": "echo setup", "timeout": 30 }
        ]
      }
    ]
  }
}"#;

    let diagnostics = validate(content);
    let cc_hk_019: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-019")
        .collect();

    assert_eq!(cc_hk_019.len(), 1);
    assert!(
        cc_hk_019[0].suggestion.is_some(),
        "CC-HK-019 should have a suggestion"
    );
    let suggestion = cc_hk_019[0].suggestion.as_ref().unwrap();
    assert!(
        suggestion.contains("SessionStart"),
        "Suggestion should mention SessionStart, got: {}",
        suggestion
    );
}

#[test]
fn test_cc_hk_019_autofix_application() {
    let content = r#"{
  "hooks": {
    "Setup": [
      {
        "hooks": [
          { "type": "command", "command": "echo setup", "timeout": 30 }
        ]
      }
    ]
  }
}"#;

    let diagnostics = validate(content);
    let cc_hk_019: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-019")
        .collect();

    assert_eq!(cc_hk_019.len(), 1);
    assert!(cc_hk_019[0].has_fixes());

    let fix = &cc_hk_019[0].fixes[0];
    let mut fixed_content = content.to_string();
    fixed_content.replace_range(fix.start_byte..fix.end_byte, &fix.replacement);

    // Verify the fixed content replaces Setup with SessionStart
    assert!(
        fixed_content.contains("\"SessionStart\""),
        "Fixed content should contain SessionStart"
    );
    assert!(
        !fixed_content.contains("\"Setup\""),
        "Fixed content should not contain Setup"
    );

    // Verify re-validation produces no CC-HK-019
    let re_diagnostics = validate(&fixed_content);
    let re_019: Vec<_> = re_diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-019")
        .collect();
    assert_eq!(re_019.len(), 0, "After fix, CC-HK-019 should not fire");
}

// ---------------------------------------------------------------------------
// CC-HK-020: HTTP hook missing `url` field
// ---------------------------------------------------------------------------

#[test]
fn test_cc_hk_020_http_hook_missing_url() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "http" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_020: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-020")
        .collect();

    assert_eq!(cc_hk_020.len(), 1);
    assert_eq!(cc_hk_020[0].level, DiagnosticLevel::Error);
    assert!(
        cc_hk_020[0]
            .message
            .contains("missing required 'url' field")
    );
}

#[test]
fn test_cc_hk_020_http_hook_with_url_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "http", "url": "https://example.com/hook" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_020: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-020")
        .collect();

    assert_eq!(cc_hk_020.len(), 0);
}

#[test]
fn test_cc_hk_020_http_hook_empty_url_string() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "http", "url": "" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_020: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-020")
        .collect();

    assert_eq!(cc_hk_020.len(), 1, "Empty url string should fire CC-HK-020");
    assert_eq!(cc_hk_020[0].level, DiagnosticLevel::Error);
}

#[test]
fn test_cc_hk_020_http_hook_non_string_url() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "http", "url": 123 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_020: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-020")
        .collect();

    assert_eq!(cc_hk_020.len(), 1, "Non-string url should fire CC-HK-020");
    assert_eq!(cc_hk_020[0].level, DiagnosticLevel::Error);
}

// ---------------------------------------------------------------------------
// CC-HK-021: Invalid `if` field
// ---------------------------------------------------------------------------

#[test]
fn test_cc_hk_021_if_field_on_non_tool_event() {
    let content = r#"{
            "hooks": {
                "Stop": [
                    {
                        "hooks": [
                            { "type": "command", "command": "echo done", "if": "tool === 'Bash'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_021: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-021")
        .collect();

    assert_eq!(cc_hk_021.len(), 1);
    assert_eq!(cc_hk_021[0].level, DiagnosticLevel::Warning);
    assert!(cc_hk_021[0].message.contains("non-tool event"));
}

#[test]
fn test_cc_hk_021_if_field_on_pre_tool_use_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo check", "if": "tool === 'Bash'" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_021: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-021")
        .collect();

    assert_eq!(cc_hk_021.len(), 0);
}

#[test]
fn test_cc_hk_021_if_field_empty_string_on_pre_tool_use() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo check", "if": "" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_021: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-021")
        .collect();

    assert_eq!(
        cc_hk_021.len(),
        1,
        "Empty 'if' string on PreToolUse should fire CC-HK-021"
    );
    assert_eq!(cc_hk_021[0].level, DiagnosticLevel::Warning);
}

#[test]
fn test_cc_hk_021_if_field_non_string_on_pre_tool_use() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo check", "if": 42 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_021: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-021")
        .collect();

    assert_eq!(
        cc_hk_021.len(),
        1,
        "Non-string 'if' on PreToolUse should fire CC-HK-021"
    );
    assert_eq!(cc_hk_021[0].level, DiagnosticLevel::Warning);
}

// ---------------------------------------------------------------------------
// CC-HK-022: Invalid `shell` value
// ---------------------------------------------------------------------------

#[test]
fn test_cc_hk_022_invalid_shell_value() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo hi", "shell": "zsh" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_022: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-022")
        .collect();

    assert_eq!(cc_hk_022.len(), 1);
    assert_eq!(cc_hk_022[0].level, DiagnosticLevel::Warning);
    assert!(cc_hk_022[0].message.contains("invalid 'shell' value"));
}

#[test]
fn test_cc_hk_022_valid_shell_bash_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo hi", "shell": "bash" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_022: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-022")
        .collect();

    assert_eq!(cc_hk_022.len(), 0);
}

#[test]
fn test_cc_hk_022_valid_shell_powershell_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo hi", "shell": "powershell" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_022: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-022")
        .collect();

    assert_eq!(cc_hk_022.len(), 0);
}

// ---------------------------------------------------------------------------
// CC-HK-023: `once` not boolean
// ---------------------------------------------------------------------------

#[test]
fn test_cc_hk_023_once_not_boolean() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo hi", "once": "yes" }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_023: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-023")
        .collect();

    assert_eq!(cc_hk_023.len(), 1);
    assert_eq!(cc_hk_023[0].level, DiagnosticLevel::Info);
    assert!(cc_hk_023[0].message.contains("non-boolean 'once' field"));
}

#[test]
fn test_cc_hk_023_once_boolean_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo hi", "once": true }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_023: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-023")
        .collect();

    assert_eq!(cc_hk_023.len(), 0);
}

#[test]
fn test_cc_hk_023_once_numeric() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            { "type": "command", "command": "echo hi", "once": 1 }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_023: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-023")
        .collect();

    assert_eq!(cc_hk_023.len(), 1);
}

// ---------------------------------------------------------------------------
// CC-HK-024: Headers with $VAR but no allowedEnvVars
// ---------------------------------------------------------------------------

#[test]
fn test_cc_hk_024_headers_env_var_without_allowed() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            {
                                "type": "http",
                                "url": "https://example.com/hook",
                                "headers": { "Authorization": "$TOKEN" }
                            }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_024: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-024")
        .collect();

    assert_eq!(cc_hk_024.len(), 1);
    assert_eq!(cc_hk_024[0].level, DiagnosticLevel::Warning);
    assert!(
        cc_hk_024[0]
            .message
            .contains("$VAR interpolation but no 'allowedEnvVars'")
    );
}

#[test]
fn test_cc_hk_024_headers_env_var_with_allowed_ok() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            {
                                "type": "http",
                                "url": "https://example.com/hook",
                                "headers": { "Authorization": "$TOKEN" },
                                "allowedEnvVars": ["TOKEN"]
                            }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_024: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-024")
        .collect();

    assert_eq!(cc_hk_024.len(), 0);
}

// CC-HK-024 only fires when $VAR appears in headers, not in the url field.
// This is by design: url values are not interpolated the same way as header
// values, so a bare $VAR in url without allowedEnvVars is not a concern.
#[test]
fn test_cc_hk_024_env_var_in_url_only_no_fire() {
    let content = r#"{
            "hooks": {
                "PreToolUse": [
                    {
                        "matcher": "Bash",
                        "hooks": [
                            {
                                "type": "http",
                                "url": "https://example.com/$SERVICE/hook"
                            }
                        ]
                    }
                ]
            }
        }"#;

    let diagnostics = validate(content);
    let cc_hk_024: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-024")
        .collect();

    assert_eq!(cc_hk_024.len(), 0);
}

// =============================================================================
// CC-HK-026 / CC-HK-027: MCP tool hook required fields
// Upstream: Claude Code v2.1.118 added `type: "mcp_tool"` hook action.
// Documented at code.claude.com/docs/en/hooks#mcp-tool-hook-fields with
// required fields `server` (string) and `tool` (string), plus optional
// `input` (object).
// =============================================================================

#[test]
fn test_cc_hk_026_missing_server_flags() {
    let content = r#"{
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "Bash",
                    "hooks": [
                        { "type": "mcp_tool", "tool": "security_scan" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let hits: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-026")
        .collect();
    assert_eq!(hits.len(), 1);
    assert_eq!(hits[0].level, DiagnosticLevel::Error);
    assert!(hits[0].message.contains("missing required 'server' field"));
}

#[test]
fn test_cc_hk_026_empty_server_flags() {
    let content = r#"{
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "Bash",
                    "hooks": [
                        { "type": "mcp_tool", "server": "", "tool": "x" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let hits: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-026")
        .collect();
    assert_eq!(hits.len(), 1);
}

#[test]
fn test_cc_hk_026_non_string_server_flags() {
    let content = r#"{
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "Bash",
                    "hooks": [
                        { "type": "mcp_tool", "server": 123, "tool": "x" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let hits: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-026")
        .collect();
    assert_eq!(hits.len(), 1);
}

#[test]
fn test_cc_hk_027_missing_tool_flags() {
    let content = r#"{
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "Bash",
                    "hooks": [
                        { "type": "mcp_tool", "server": "my_server" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let hits: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-027")
        .collect();
    assert_eq!(hits.len(), 1);
    assert!(hits[0].message.contains("missing required 'tool' field"));
}

#[test]
fn test_cc_hk_027_empty_tool_flags() {
    let content = r#"{
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "Bash",
                    "hooks": [
                        { "type": "mcp_tool", "server": "my_server", "tool": "" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let hits: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-027")
        .collect();
    assert_eq!(hits.len(), 1);
}

#[test]
fn test_cc_hk_mcp_tool_valid_hook_does_not_flag() {
    // Full valid mcp_tool hook from the upstream docs example.
    let content = r#"{
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "Edit",
                    "hooks": [
                        {
                            "type": "mcp_tool",
                            "server": "my_server",
                            "tool": "security_scan",
                            "input": { "file_path": "${tool_input.file_path}" }
                        }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let hits: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-026" || d.rule == "CC-HK-027")
        .collect();
    assert!(
        hits.is_empty(),
        "valid mcp_tool hook must not flag, got {:?}",
        hits.iter()
            .map(|d| (&d.rule, &d.message))
            .collect::<Vec<_>>()
    );
}

#[test]
fn test_cc_hk_mcp_tool_does_not_trigger_on_command_hook() {
    // Guardrail: CC-HK-026/027 must only fire on type:mcp_tool hooks,
    // not on command/http/prompt/agent types.
    let content = r#"{
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "Bash",
                    "hooks": [
                        { "type": "command", "command": "echo hi" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let hits: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-026" || d.rule == "CC-HK-027")
        .collect();
    assert!(hits.is_empty());
}

#[test]
fn test_cc_hk_026_can_be_disabled_independently_of_027() {
    let mut config = LintConfig::default();
    config.rules_mut().disabled_rules = vec!["CC-HK-026".to_string()];
    // Only server is missing AND tool is missing - both would normally fire.
    // With CC-HK-026 disabled, only CC-HK-027 should fire.
    let content = r#"{
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "Edit",
                    "hooks": [
                        { "type": "mcp_tool" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate_with_config(content, &config);
    let cc_026: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-026")
        .collect();
    let cc_027: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-027")
        .collect();
    assert!(
        cc_026.is_empty(),
        "CC-HK-026 disabled - no diagnostic expected"
    );
    assert_eq!(
        cc_027.len(),
        1,
        "CC-HK-027 still enabled - 'tool missing' diagnostic expected"
    );
}

#[test]
fn test_cc_hk_027_can_be_disabled_independently_of_026() {
    let mut config = LintConfig::default();
    config.rules_mut().disabled_rules = vec!["CC-HK-027".to_string()];
    let content = r#"{
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "Edit",
                    "hooks": [
                        { "type": "mcp_tool" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate_with_config(content, &config);
    let cc_026: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-026")
        .collect();
    let cc_027: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-027")
        .collect();
    assert_eq!(cc_026.len(), 1);
    assert!(cc_027.is_empty());
}

#[test]
fn test_cc_hk_mcp_tool_both_missing_flags_both() {
    let content = r#"{
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "Edit",
                    "hooks": [
                        { "type": "mcp_tool" }
                    ]
                }
            ]
        }
    }"#;
    let diagnostics = validate(content);
    let cc_026: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-026")
        .collect();
    let cc_027: Vec<_> = diagnostics
        .iter()
        .filter(|d| d.rule == "CC-HK-027")
        .collect();
    assert_eq!(cc_026.len(), 1);
    assert_eq!(cc_027.len(), 1);
}
