//! Benchmarks for the validation pipeline hot paths.
//!
//! Run with: cargo bench --package agnix-core
//!
//! These benchmarks measure:
//! - File type detection speed
//! - Validator registry construction
//! - Single file validation (various file types)
//! - Project validation throughput
//! - Frontmatter parsing speed
//! - Scale testing (100, 1000 files)
//! - Memory usage tracking
//!
//! For deterministic CI benchmarks, use iai_validation.rs instead.
//! This file provides wall-clock timing for local development iteration.

mod fixtures;

use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use std::hint::black_box;
use std::path::Path;
use tempfile::TempDir;

use agnix_core::{
    LintConfig, ValidatorRegistry, detect_file_type, validate_file, validate_file_with_registry,
    validate_project,
};

use fixtures::{create_memory_test_project, create_scale_project};

/// Benchmark file type detection - the first step in validation dispatch.
fn bench_detect_file_type(c: &mut Criterion) {
    let paths = [
        ("skill", Path::new("SKILL.md")),
        ("claude_md", Path::new("CLAUDE.md")),
        ("agents_md", Path::new("AGENTS.md")),
        ("hooks", Path::new("settings.json")),
        ("plugin", Path::new("plugin.json")),
        ("mcp", Path::new("mcp.json")),
        ("generic_md", Path::new("README.md")),
        ("unknown", Path::new("file.txt")),
        // Deep paths
        ("nested_skill", Path::new(".claude/skills/deploy/SKILL.md")),
        ("nested_agent", Path::new("agents/helper.md")),
    ];

    let mut group = c.benchmark_group("detect_file_type");
    for (name, path) in paths {
        group.bench_with_input(BenchmarkId::new("path", name), path, |b, p| {
            b.iter(|| detect_file_type(black_box(p)))
        });
    }
    group.finish();
}

/// Benchmark validator registry construction.
fn bench_validator_registry(c: &mut Criterion) {
    c.bench_function("ValidatorRegistry::with_defaults", |b| {
        b.iter(ValidatorRegistry::with_defaults)
    });
}

/// Benchmark single file validation with realistic content.
fn bench_validate_single_file(c: &mut Criterion) {
    let temp = TempDir::new().unwrap();
    let config = LintConfig::default();
    let registry = ValidatorRegistry::with_defaults();

    // Create test files with realistic content
    let skill_content = r#"---
name: code-review
description: Use when reviewing code for quality, style, and potential issues
version: 1.0.0
model: sonnet
---

# Code Review Skill

This skill helps review code for:
- Style consistency
- Potential bugs
- Performance issues
- Security vulnerabilities

## Usage

Invoke this skill when you need a thorough code review.
"#;

    let claude_md_content = r#"# Project Memory

## Architecture
- Rust workspace with multiple crates
- Core validation engine in agnix-core
- CLI interface in agnix-cli

## Commands
```bash
cargo test
cargo build --release
```

## Guidelines
- Follow Rust idioms
- Keep functions small
- Write tests for new features
"#;

    let mcp_content = r#"{
    "name": "file-search",
    "description": "Search for files in the workspace by name or content pattern",
    "inputSchema": {
        "type": "object",
        "properties": {
            "pattern": {
                "type": "string",
                "description": "Search pattern (glob or regex)"
            },
            "type": {
                "type": "string",
                "enum": ["glob", "regex"],
                "default": "glob"
            }
        },
        "required": ["pattern"]
    }
}"#;

    let hooks_content = r#"{
    "hooks": {
        "PreToolExecution": [
            {
                "matcher": "Bash",
                "hooks": [
                    {
                        "type": "command",
                        "command": "echo 'Running command'"
                    }
                ]
            }
        ]
    }
}"#;

    // Write test files
    let skill_path = temp.path().join("SKILL.md");
    let claude_path = temp.path().join("CLAUDE.md");
    let mcp_path = temp.path().join("tools.mcp.json");
    let hooks_path = temp.path().join("settings.json");

    std::fs::write(&skill_path, skill_content).unwrap();
    std::fs::write(&claude_path, claude_md_content).unwrap();
    std::fs::write(&mcp_path, mcp_content).unwrap();
    std::fs::write(&hooks_path, hooks_content).unwrap();

    let mut group = c.benchmark_group("validate_single_file");

    // Set throughput based on file size
    group.throughput(Throughput::Bytes(skill_content.len() as u64));
    group.bench_function("skill_md", |b| {
        b.iter(|| validate_file_with_registry(black_box(&skill_path), &config, &registry))
    });

    group.throughput(Throughput::Bytes(claude_md_content.len() as u64));
    group.bench_function("claude_md", |b| {
        b.iter(|| validate_file_with_registry(black_box(&claude_path), &config, &registry))
    });

    group.throughput(Throughput::Bytes(mcp_content.len() as u64));
    group.bench_function("mcp_json", |b| {
        b.iter(|| validate_file_with_registry(black_box(&mcp_path), &config, &registry))
    });

    group.throughput(Throughput::Bytes(hooks_content.len() as u64));
    group.bench_function("hooks_json", |b| {
        b.iter(|| validate_file_with_registry(black_box(&hooks_path), &config, &registry))
    });

    group.finish();
}

/// Benchmark project validation with multiple files.
fn bench_validate_project(c: &mut Criterion) {
    let temp = TempDir::new().unwrap();
    let config = LintConfig::default();

    // Create a realistic project structure
    let skills = [
        ("code-review", "Use when reviewing code"),
        ("test-runner", "Use when running tests"),
        ("deploy", "Use when deploying to production"),
        ("refactor", "Use when refactoring code"),
        ("debug", "Use when debugging issues"),
    ];

    for (name, desc) in skills {
        let skill_dir = temp.path().join("skills").join(name);
        std::fs::create_dir_all(&skill_dir).unwrap();
        std::fs::write(
            skill_dir.join("SKILL.md"),
            format!(
                "---\nname: {}\ndescription: {}\n---\n# {}\n\nSkill body content.",
                name, desc, name
            ),
        )
        .unwrap();
    }

    // Add CLAUDE.md
    std::fs::write(
        temp.path().join("CLAUDE.md"),
        "# Project\n\n## Guidelines\n\n- Write clean code\n- Test everything",
    )
    .unwrap();

    // Add MCP config
    std::fs::write(
        temp.path().join("mcp.json"),
        r#"{"name": "tool", "description": "A tool", "inputSchema": {"type": "object"}}"#,
    )
    .unwrap();

    let mut group = c.benchmark_group("validate_project");

    // Small project (5 skills + 2 other files = 7 files)
    group.throughput(Throughput::Elements(7));
    group.bench_function("small_project_7_files", |b| {
        b.iter(|| validate_project(black_box(temp.path()), &config))
    });

    group.finish();

    // Create a larger project
    let large_temp = TempDir::new().unwrap();
    for i in 0..50 {
        let skill_dir = large_temp
            .path()
            .join("skills")
            .join(format!("skill-{}", i));
        std::fs::create_dir_all(&skill_dir).unwrap();
        std::fs::write(
            skill_dir.join("SKILL.md"),
            format!(
                "---\nname: skill-{}\ndescription: Skill number {}\n---\n# Skill {}\n\nBody.",
                i, i, i
            ),
        )
        .unwrap();
    }

    std::fs::write(
        large_temp.path().join("CLAUDE.md"),
        "# Project\n\nGuidelines.",
    )
    .unwrap();

    let mut group = c.benchmark_group("validate_project");
    group.throughput(Throughput::Elements(51));
    group.bench_function("medium_project_51_files", |b| {
        b.iter(|| validate_project(black_box(large_temp.path()), &config))
    });

    group.finish();
}

/// Benchmark validation with and without registry caching.
fn bench_registry_caching(c: &mut Criterion) {
    let temp = TempDir::new().unwrap();
    let config = LintConfig::default();

    let skill_path = temp.path().join("SKILL.md");
    std::fs::write(
        &skill_path,
        "---\nname: test\ndescription: Test skill\n---\n# Test",
    )
    .unwrap();

    let mut group = c.benchmark_group("registry_caching");

    // Without caching - creates new registry each time
    group.bench_function("without_cache", |b| {
        b.iter(|| validate_file(black_box(&skill_path), &config))
    });

    // With caching - reuses registry
    let registry = ValidatorRegistry::with_defaults();
    group.bench_function("with_cache", |b| {
        b.iter(|| validate_file_with_registry(black_box(&skill_path), &config, &registry))
    });

    group.finish();
}

/// Benchmark frontmatter parsing speed.
fn bench_frontmatter_parsing(c: &mut Criterion) {
    use agnix_core::__internal::split_frontmatter;

    let small_frontmatter = "---\nname: test\n---\nBody";

    let medium_frontmatter = r#"---
name: complex-skill
description: A more complex skill with multiple fields
version: 1.0.0
model: sonnet
triggers:
  - pattern: "review code"
  - pattern: "check this"
dependencies:
  - other-skill
  - helper-skill
---

# Complex Skill

This is the body with more content.
"#;

    let large_frontmatter = format!(
        "---\nname: large\ndescription: {}\n---\n{}",
        "A".repeat(500),
        "Body content. ".repeat(100)
    );

    let mut group = c.benchmark_group("frontmatter_parsing");

    group.throughput(Throughput::Bytes(small_frontmatter.len() as u64));
    group.bench_function("small_50_bytes", |b| {
        b.iter(|| split_frontmatter(black_box(small_frontmatter)))
    });

    group.throughput(Throughput::Bytes(medium_frontmatter.len() as u64));
    group.bench_function("medium_300_bytes", |b| {
        b.iter(|| split_frontmatter(black_box(medium_frontmatter)))
    });

    group.throughput(Throughput::Bytes(large_frontmatter.len() as u64));
    group.bench_function("large_2kb", |b| {
        b.iter(|| split_frontmatter(black_box(&large_frontmatter)))
    });

    group.finish();
}

fn bench_import_cache(c: &mut Criterion) {
    use std::fs;
    use tempfile::TempDir;

    // Create a temporary directory with files that have overlapping imports
    // This simulates a real-world scenario where multiple markdown files
    // reference the same set of shared documentation files.
    let temp = TempDir::new().unwrap();

    // Create shared files that will be imported multiple times
    for i in 0..5 {
        let content = format!("# Shared Doc {}\n\nShared content {}", i, i);
        fs::write(temp.path().join(format!("shared{}.md", i)), content).unwrap();
    }

    // Create main files that each import all shared files
    for i in 0..10 {
        let imports: Vec<String> = (0..5).map(|j| format!("@shared{}.md", j)).collect();
        let content = format!("# Main Doc {}\n\nReferences: {}\n", i, imports.join(", "));
        fs::write(temp.path().join(format!("main{}.md", i)), content).unwrap();
    }

    // Create a CLAUDE.md that references all main files (to trigger import traversal)
    let main_imports: Vec<String> = (0..10).map(|i| format!("@main{}.md", i)).collect();
    fs::write(
        temp.path().join("CLAUDE.md"),
        format!("# Project\n\nFiles: {}\n", main_imports.join(", ")),
    )
    .unwrap();

    let mut group = c.benchmark_group("import_cache");

    // Benchmark project validation with shared cache (default behavior)
    group.bench_function("project_with_shared_cache", |b| {
        b.iter(|| {
            let config = LintConfig::default();
            validate_project(black_box(temp.path()), black_box(&config))
        })
    });

    // Benchmark single-file validation (no shared cache, baseline)
    // This shows the overhead of re-parsing imports for each file
    group.bench_function("single_file_no_cache", |b| {
        let claude_path = temp.path().join("CLAUDE.md");
        b.iter(|| {
            let config = LintConfig::default();
            validate_file(black_box(&claude_path), black_box(&config))
        })
    });

    group.finish();
}

/// Benchmark scale testing with 100 files.
///
/// Measures parallelization efficiency with a realistic project distribution:
/// 70% SKILL.md, 15% hooks, 10% MCP, 5% misc.
fn bench_scale_100_files(c: &mut Criterion) {
    let temp = create_scale_project(100);
    let config = LintConfig::default();

    let mut group = c.benchmark_group("scale_testing");
    group.sample_size(20); // Reduce sample size for large tests
    group.throughput(Throughput::Elements(100));
    group.bench_function("100_files", |b| {
        b.iter(|| validate_project(black_box(temp.path()), &config))
    });
    group.finish();
}

/// Benchmark scale testing with 1000 files.
///
/// Target: < 5 seconds wall-clock time.
/// This validates the linear scaling behavior of the validation pipeline.
fn bench_scale_1000_files(c: &mut Criterion) {
    let temp = create_scale_project(1000);
    let config = LintConfig::default();

    let mut group = c.benchmark_group("scale_testing");
    group.sample_size(10); // Reduce sample size for very large tests
    group.throughput(Throughput::Elements(1000));
    group.bench_function("1000_files", |b| {
        b.iter(|| validate_project(black_box(temp.path()), &config))
    });
    group.finish();
}

/// Benchmark memory usage during validation.
///
/// Tests validation performance with a project optimized for memory stress:
/// - Deep import chains between files
/// - Multiple files referencing shared documentation
/// - Stresses the ImportCache and diagnostic collection
///
/// Target: < 100MB peak for large repos.
///
/// Note: Full memory tracking requires setting up a global allocator with
/// tracking-allocator. This benchmark uses the project structure to stress
/// memory-intensive code paths.
fn bench_memory_usage(c: &mut Criterion) {
    // Create test project optimized for memory testing (deep import chains)
    let temp = create_memory_test_project();
    let config = LintConfig::default();

    let mut group = c.benchmark_group("memory_usage");
    group.sample_size(10);

    group.bench_function("import_chain_project", |b| {
        b.iter(|| {
            let result = validate_project(black_box(temp.path()), &config);
            black_box(result)
        });
    });

    group.finish();
}

/// Benchmark single file validation against performance target.
///
/// Target: < 100ms (typically < 10ms).
fn bench_single_file_target(c: &mut Criterion) {
    use fixtures::create_single_skill_file;

    let temp = create_single_skill_file();
    let config = LintConfig::default();
    let registry = ValidatorRegistry::with_defaults();
    let skill_path = temp.path().join("SKILL.md");

    let mut group = c.benchmark_group("performance_targets");
    group.bench_function("single_file_under_100ms", |b| {
        b.iter(|| validate_file_with_registry(black_box(&skill_path), &config, &registry))
    });
    group.finish();
}

/// Benchmark auto-fix span finding through hook validation.
///
/// This exercises the span_utils byte-scanning functions that replaced
/// dynamic Regex::new() calls. The content is crafted to trigger multiple
/// auto-fix code paths (event key lookup, field line spans, matcher spans).
fn bench_hooks_autofix_spans(c: &mut Criterion) {
    let temp = TempDir::new().unwrap();
    let config = LintConfig::default();
    let registry = ValidatorRegistry::with_defaults();

    // Content with issues that trigger auto-fix span finding:
    // - Invalid event name (find_event_key_span)
    // - async on non-command hook (find_unique_json_field_line)
    // - matcher line spans
    let hooks_content = r#"{
    "hooks": {
        "InvalidEvent": [
            {
                "matcher": "Bash",
                "hooks": [
                    {
                        "type": "prompt",
                        "async": true,
                        "prompt": "Review this code"
                    }
                ]
            }
        ],
        "PreToolExecution": [
            {
                "matcher": "Write",
                "hooks": [
                    {
                        "type": "command",
                        "command": "echo 'pre-write check'"
                    }
                ]
            }
        ]
    }
}"#;

    let hooks_path = temp.path().join("settings.json");
    std::fs::write(&hooks_path, hooks_content).unwrap();

    let mut group = c.benchmark_group("hooks_autofix_spans");
    group.throughput(Throughput::Bytes(hooks_content.len() as u64));
    group.bench_function("hooks_with_fixable_issues", |b| {
        b.iter(|| validate_file_with_registry(black_box(&hooks_path), &config, &registry))
    });
    group.finish();
}

/// Benchmark `is_instruction_file()` - called once per file during project walk.
///
/// Measures the allocation-free path-component implementation against a
/// representative mix of paths: common non-matches (majority of files in any
/// repo), direct filename matches, and directory-based matches.
fn bench_is_instruction_file(c: &mut Criterion) {
    use agnix_core::__internal::is_instruction_file;

    let paths: Vec<(&str, std::path::PathBuf)> = vec![
        // Common non-matches (the hot path - most files are not instruction files)
        ("rs_file", Path::new("src/main.rs").to_path_buf()),
        ("toml_file", Path::new("Cargo.toml").to_path_buf()),
        ("readme", Path::new("README.md").to_path_buf()),
        (
            "nested_rs",
            Path::new("crates/core/src/lib.rs").to_path_buf(),
        ),
        ("json_file", Path::new("package.json").to_path_buf()),
        (
            "deep_path",
            Path::new("a/b/c/d/e/f/g/file.txt").to_path_buf(),
        ),
        ("gitignore", Path::new(".gitignore").to_path_buf()),
        ("license", Path::new("LICENSE").to_path_buf()),
        // Backup files (early rejection)
        ("bak_file", Path::new("CLAUDE.md.bak").to_path_buf()),
        ("swp_file", Path::new("AGENTS.md.swp").to_path_buf()),
        ("tilde_file", Path::new("CLAUDE.md~").to_path_buf()),
        // Direct filename matches
        ("claude_md", Path::new("CLAUDE.md").to_path_buf()),
        ("agents_md", Path::new("AGENTS.md").to_path_buf()),
        ("gemini_md", Path::new("gemini.md").to_path_buf()),
        ("clinerules", Path::new(".clinerules").to_path_buf()),
        (
            "clinerules_folder_md",
            Path::new(".clinerules/rules.md").to_path_buf(),
        ),
        (
            "clinerules_folder_txt",
            Path::new(".clinerules/rules.txt").to_path_buf(),
        ),
        // Directory-based matches
        (
            "cursor_mdc",
            Path::new(".cursor/rules/test.mdc").to_path_buf(),
        ),
        (
            "cursor_deep",
            Path::new("project/.cursor/rules/deep/file.mdc").to_path_buf(),
        ),
        (
            "github_copilot",
            Path::new(".github/copilot-instructions.md").to_path_buf(),
        ),
        ("opencode", Path::new(".opencode/config.md").to_path_buf()),
        // False-positive guard: substring in filename, not a directory
        (
            "cursor_substring",
            Path::new("my.cursor-notes.mdc").to_path_buf(),
        ),
    ];

    let mut group = c.benchmark_group("is_instruction_file");
    for (name, path) in &paths {
        group.bench_with_input(BenchmarkId::new("path", *name), path, |b, p| {
            b.iter(|| is_instruction_file(black_box(p)))
        });
    }
    group.finish();
}

criterion_group!(
    benches,
    bench_detect_file_type,
    bench_validator_registry,
    bench_validate_single_file,
    bench_validate_project,
    bench_registry_caching,
    bench_frontmatter_parsing,
    bench_import_cache,
    bench_scale_100_files,
    bench_scale_1000_files,
    bench_memory_usage,
    bench_single_file_target,
    bench_hooks_autofix_spans,
    bench_is_instruction_file,
);
criterion_main!(benches);
