"""Tests for MCP Benchmark Runner."""

from __future__ import annotations

import json
import tempfile
from pathlib import Path

import pytest

from mcpaudit.benchmark import (
    BenchmarkResult,
    FileTiming,
    LanguageTiming,
    MemoryProfile,
    RuleTiming,
    _collect_files,
    _detect_language,
    _get_rss_kb,
    _percentile,
    format_benchmark_html,
    format_benchmark_json,
    format_benchmark_text,
    run_benchmark,
)


# ---------------------------------------------------------------------------
# RuleTiming
# ---------------------------------------------------------------------------


class TestRuleTiming:
    def test_to_dict(self):
        rt = RuleTiming(
            rule_id="CMD-001",
            rule_name="Command Injection",
            total_ms=150.5,
            files_checked=10,
            avg_ms=15.05,
            max_ms=50.2,
            min_ms=1.1,
            is_slow=False,
        )
        d = rt.to_dict()
        assert d["rule_id"] == "CMD-001"
        assert d["total_ms"] == 150.5
        assert d["files_checked"] == 10
        assert d["avg_ms"] == 15.05
        assert d["is_slow"] is False

    def test_default_values(self):
        rt = RuleTiming(rule_id="TEST", rule_name="Test Rule")
        assert rt.total_ms == 0.0
        assert rt.files_checked == 0
        assert rt.is_slow is False

    def test_slow_rule(self):
        rt = RuleTiming(
            rule_id="SLOW-001",
            rule_name="Slow Rule",
            avg_ms=150.0,
            is_slow=True,
        )
        d = rt.to_dict()
        assert d["is_slow"] is True


# ---------------------------------------------------------------------------
# FileTiming
# ---------------------------------------------------------------------------


class TestFileTiming:
    def test_to_dict(self):
        ft = FileTiming(
            file="test.py",
            language="python",
            scan_ms=25.5,
            lines=100,
            findings=3,
        )
        d = ft.to_dict()
        assert d["file"] == "test.py"
        assert d["language"] == "python"
        assert d["scan_ms"] == 25.5
        assert d["lines"] == 100
        assert d["findings"] == 3


# ---------------------------------------------------------------------------
# LanguageTiming
# ---------------------------------------------------------------------------


class TestLanguageTiming:
    def test_to_dict(self):
        lt = LanguageTiming(
            language="python",
            files=5,
            total_ms=100.0,
            avg_ms=20.0,
            total_lines=500,
            total_findings=10,
        )
        d = lt.to_dict()
        assert d["language"] == "python"
        assert d["files"] == 5
        assert d["avg_ms"] == 20.0

    def test_defaults(self):
        lt = LanguageTiming(language="go")
        assert lt.files == 0
        assert lt.total_ms == 0.0


# ---------------------------------------------------------------------------
# MemoryProfile
# ---------------------------------------------------------------------------


class TestMemoryProfile:
    def test_to_dict(self):
        mp = MemoryProfile(
            rss_before_kb=10000,
            rss_after_kb=12000,
            rss_peak_kb=15000,
            rss_delta_kb=2000,
        )
        d = mp.to_dict()
        assert d["rss_before_kb"] == 10000
        assert d["rss_delta_kb"] == 2000

    def test_defaults(self):
        mp = MemoryProfile()
        assert mp.rss_before_kb == 0
        assert mp.rss_delta_kb == 0


# ---------------------------------------------------------------------------
# BenchmarkResult
# ---------------------------------------------------------------------------


class TestBenchmarkResult:
    def test_to_dict(self):
        result = BenchmarkResult(
            iterations=5,
            total_ms=500.0,
            avg_iteration_ms=100.0,
            target="/test",
        )
        d = result.to_dict()
        assert d["iterations"] == 5
        assert d["total_ms"] == 500.0
        assert d["avg_iteration_ms"] == 100.0
        assert d["target"] == "/test"
        assert d["files_scanned"] == 0

    def test_with_files(self):
        result = BenchmarkResult(
            files=[
                FileTiming("a.py", "python", 10.0, 50, 1),
                FileTiming("b.ts", "typescript", 20.0, 100, 2),
            ],
        )
        d = result.to_dict()
        assert d["files_scanned"] == 2
        assert len(d["files"]) == 2

    def test_with_slow_rules(self):
        result = BenchmarkResult(
            slow_rules=[
                RuleTiming("SLOW-001", "Slow", avg_ms=200.0, is_slow=True),
            ],
        )
        d = result.to_dict()
        assert len(d["slow_rules"]) == 1

    def test_percentiles_rounding(self):
        result = BenchmarkResult(
            percentiles={"p50": 10.123456, "p90": 50.789},
        )
        d = result.to_dict()
        assert d["percentiles"]["p50"] == 10.12
        assert d["percentiles"]["p90"] == 50.79

    def test_default_result(self):
        result = BenchmarkResult()
        d = result.to_dict()
        assert d["iterations"] == 1
        assert d["files_scanned"] == 0


# ---------------------------------------------------------------------------
# _detect_language
# ---------------------------------------------------------------------------


class TestDetectLanguage:
    def test_python(self):
        assert _detect_language(Path("test.py")) == "python"

    def test_typescript(self):
        assert _detect_language(Path("test.ts")) == "typescript"

    def test_tsx(self):
        assert _detect_language(Path("test.tsx")) == "typescript"

    def test_javascript(self):
        assert _detect_language(Path("test.js")) == "javascript"

    def test_jsx(self):
        assert _detect_language(Path("test.jsx")) == "javascript"

    def test_go(self):
        assert _detect_language(Path("test.go")) == "go"

    def test_json(self):
        assert _detect_language(Path("test.json")) == "json"

    def test_yaml(self):
        assert _detect_language(Path("test.yaml")) == "yaml"

    def test_yml(self):
        assert _detect_language(Path("test.yml")) == "yaml"

    def test_unknown(self):
        assert _detect_language(Path("test.txt")) == "unknown"


# ---------------------------------------------------------------------------
# _collect_files
# ---------------------------------------------------------------------------


class TestCollectFiles:
    def test_single_file(self, tmp_path):
        f = tmp_path / "test.py"
        f.write_text("# test\n")
        files = _collect_files(f)
        assert len(files) == 1
        assert files[0] == f

    def test_directory(self, tmp_path):
        (tmp_path / "a.py").write_text("# a\n")
        (tmp_path / "b.ts").write_text("// b\n")
        (tmp_path / "c.txt").write_text("ignored\n")
        files = _collect_files(tmp_path)
        assert len(files) == 2
        extensions = {f.suffix for f in files}
        assert ".py" in extensions
        assert ".ts" in extensions
        assert ".txt" not in extensions

    def test_nested_directory(self, tmp_path):
        sub = tmp_path / "sub"
        sub.mkdir()
        (tmp_path / "a.py").write_text("# a\n")
        (sub / "b.py").write_text("# b\n")
        files = _collect_files(tmp_path)
        assert len(files) == 2

    def test_empty_directory(self, tmp_path):
        files = _collect_files(tmp_path)
        assert len(files) == 0


# ---------------------------------------------------------------------------
# _percentile
# ---------------------------------------------------------------------------


class TestPercentile:
    def test_empty_data(self):
        assert _percentile([], 50) == 0.0

    def test_single_value(self):
        assert _percentile([10.0], 50) == 10.0

    def test_median_even(self):
        result = _percentile([1.0, 2.0, 3.0, 4.0], 50)
        assert abs(result - 2.5) < 0.01

    def test_p0(self):
        result = _percentile([1.0, 2.0, 3.0], 0)
        assert result == 1.0

    def test_p100(self):
        result = _percentile([1.0, 2.0, 3.0], 100)
        assert result == 3.0

    def test_p90(self):
        data = sorted([float(i) for i in range(100)])
        result = _percentile(data, 90)
        assert 88.0 < result < 92.0


# ---------------------------------------------------------------------------
# _get_rss_kb
# ---------------------------------------------------------------------------


class TestGetRssKb:
    def test_returns_positive(self):
        rss = _get_rss_kb()
        # On most systems, RSS should be > 0
        assert rss >= 0


# ---------------------------------------------------------------------------
# run_benchmark
# ---------------------------------------------------------------------------


class TestRunBenchmark:
    def test_benchmark_file(self, tmp_path):
        f = tmp_path / "test.py"
        f.write_text("""
import os
import subprocess

def run_command(cmd):
    subprocess.call(cmd, shell=True)
""")
        result = run_benchmark(str(f), iterations=1)
        assert result.iterations == 1
        assert len(result.files) == 1
        assert result.files[0].language == "python"
        assert result.total_ms > 0

    def test_benchmark_directory(self, tmp_path):
        (tmp_path / "a.py").write_text("x = 1\n")
        (tmp_path / "b.py").write_text("y = 2\n")
        result = run_benchmark(str(tmp_path), iterations=1)
        assert len(result.files) == 2

    def test_benchmark_empty_dir(self, tmp_path):
        result = run_benchmark(str(tmp_path), iterations=3)
        assert result.iterations == 3
        assert len(result.files) == 0

    def test_benchmark_memory_profile(self, tmp_path):
        f = tmp_path / "test.py"
        f.write_text("x = 1\n")
        result = run_benchmark(str(f), iterations=1)
        assert result.memory.rss_before_kb >= 0
        assert result.memory.rss_after_kb >= 0

    def test_benchmark_percentiles(self, tmp_path):
        for i in range(5):
            (tmp_path / f"test_{i}.py").write_text(f"x = {i}\n")
        result = run_benchmark(str(tmp_path), iterations=1)
        assert "p50" in result.percentiles
        assert "p90" in result.percentiles
        assert "min" in result.percentiles
        assert "max" in result.percentiles

    def test_benchmark_languages(self, tmp_path):
        (tmp_path / "a.py").write_text("x = 1\n")
        result = run_benchmark(str(tmp_path), iterations=1)
        assert len(result.languages) >= 1
        py_lang = [lt for lt in result.languages if lt.language == "python"]
        assert len(py_lang) == 1
        assert py_lang[0].files == 1

    def test_benchmark_multiple_iterations(self, tmp_path):
        f = tmp_path / "test.py"
        f.write_text("x = 1\n")
        result = run_benchmark(str(f), iterations=3)
        assert result.iterations == 3
        assert result.total_ms > 0
        assert result.avg_iteration_ms > 0
        # Files only recorded once (first iteration)
        assert len(result.files) == 1

    def test_nonexistent_path(self):
        result = run_benchmark("/nonexistent/path", iterations=1)
        assert len(result.files) == 0


# ---------------------------------------------------------------------------
# format_benchmark_text
# ---------------------------------------------------------------------------


class TestFormatBenchmarkText:
    def test_basic_output(self):
        result = BenchmarkResult(
            iterations=5,
            total_ms=500.0,
            avg_iteration_ms=100.0,
            target="/test/dir",
        )
        text = format_benchmark_text(result)
        assert "Benchmark Runner" in text
        assert "/test/dir" in text
        assert "500.0ms" in text
        assert "Iterations: 5" in text

    def test_with_files(self):
        result = BenchmarkResult(
            files=[
                FileTiming("test.py", "python", 25.5, 100, 3),
            ],
            percentiles={"min": 25.5, "p50": 25.5, "max": 25.5},
        )
        text = format_benchmark_text(result)
        assert "test.py" in text
        assert "100 lines" in text

    def test_with_slow_rules(self):
        result = BenchmarkResult(
            slow_rules=[
                RuleTiming("SLOW-001", "Slow Rule", avg_ms=200.0, max_ms=500.0, is_slow=True),
            ],
        )
        text = format_benchmark_text(result)
        assert "SLOW RULES" in text
        assert "SLOW-001" in text

    def test_with_languages(self):
        result = BenchmarkResult(
            languages=[
                LanguageTiming("python", files=3, total_ms=100.0, avg_ms=33.3),
            ],
        )
        text = format_benchmark_text(result)
        assert "python" in text
        assert "By Language" in text

    def test_memory_section(self):
        result = BenchmarkResult(
            memory=MemoryProfile(
                rss_before_kb=10000,
                rss_after_kb=12000,
                rss_peak_kb=15000,
                rss_delta_kb=2000,
            ),
        )
        text = format_benchmark_text(result)
        assert "Memory Profile" in text
        assert "10,000" in text


# ---------------------------------------------------------------------------
# format_benchmark_json
# ---------------------------------------------------------------------------


class TestFormatBenchmarkJson:
    def test_json_structure(self):
        result = BenchmarkResult(
            iterations=3,
            total_ms=300.0,
            target="/test",
        )
        text = format_benchmark_json(result)
        data = json.loads(text)
        assert "benchmark" in data
        assert data["benchmark"]["iterations"] == 3
        assert data["benchmark"]["total_ms"] == 300.0
        assert data["benchmark"]["target"] == "/test"

    def test_json_with_files(self):
        result = BenchmarkResult(
            files=[FileTiming("a.py", "python", 10.0, 50, 1)],
        )
        text = format_benchmark_json(result)
        data = json.loads(text)
        assert len(data["benchmark"]["files"]) == 1
        assert data["benchmark"]["files"][0]["language"] == "python"

    def test_json_empty_result(self):
        result = BenchmarkResult()
        text = format_benchmark_json(result)
        data = json.loads(text)
        assert data["benchmark"]["files_scanned"] == 0


# ---------------------------------------------------------------------------
# format_benchmark_html
# ---------------------------------------------------------------------------


class TestFormatBenchmarkHtml:
    def test_html_structure(self):
        result = BenchmarkResult(
            iterations=5,
            total_ms=500.0,
            target="/test",
            files=[FileTiming("test.py", "python", 25.5, 100, 3)],
            rules=[RuleTiming("CMD-001", "Command Injection", avg_ms=10.0, max_ms=20.0, files_checked=1)],
            percentiles={"p50": 25.5, "p90": 25.5, "max": 25.5},
        )
        html = format_benchmark_html(result)
        assert "<!DOCTYPE html>" in html
        assert "Benchmark Report" in html
        assert "/test" in html
        assert "test.py" in html
        assert "CMD-001" in html

    def test_html_with_slow_rules(self):
        result = BenchmarkResult(
            rules=[
                RuleTiming("SLOW-001", "Slow Rule", avg_ms=200.0, max_ms=500.0, is_slow=True),
            ],
        )
        html = format_benchmark_html(result)
        assert "SLOW" in html

    def test_html_empty_result(self):
        result = BenchmarkResult(target="/empty")
        html = format_benchmark_html(result)
        assert "<!DOCTYPE html>" in html
        assert "/empty" in html

    def test_html_percentile_bars(self):
        result = BenchmarkResult(
            percentiles={"p50": 10.0, "p90": 50.0, "max": 100.0},
        )
        html = format_benchmark_html(result)
        assert "p50" in html
        assert "p90" in html
