"""MCP Benchmark Runner — Performance benchmarking for the scanner.

Measures scan time per file, per rule, and per language. Generates performance
reports with percentiles. Detects slow rules (>100ms per file). Includes
basic memory profiling (peak RSS before/after).

Zero external dependencies — pure Python 3.10+ stdlib.
"""

from __future__ import annotations

import json
import resource
import statistics
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any


# ---------------------------------------------------------------------------
# Data models
# ---------------------------------------------------------------------------

@dataclass
class RuleTiming:
    """Timing data for a single rule."""

    rule_id: str
    rule_name: str
    total_ms: float = 0.0
    files_checked: int = 0
    avg_ms: float = 0.0
    max_ms: float = 0.0
    min_ms: float = 0.0
    is_slow: bool = False  # > 100ms per file

    def to_dict(self) -> dict[str, Any]:
        return {
            "rule_id": self.rule_id,
            "rule_name": self.rule_name,
            "total_ms": round(self.total_ms, 2),
            "files_checked": self.files_checked,
            "avg_ms": round(self.avg_ms, 2),
            "max_ms": round(self.max_ms, 2),
            "min_ms": round(self.min_ms, 2),
            "is_slow": self.is_slow,
        }


@dataclass
class FileTiming:
    """Timing data for a single file scan."""

    file: str
    language: str
    scan_ms: float
    lines: int
    findings: int

    def to_dict(self) -> dict[str, Any]:
        return {
            "file": self.file,
            "language": self.language,
            "scan_ms": round(self.scan_ms, 2),
            "lines": self.lines,
            "findings": self.findings,
        }


@dataclass
class LanguageTiming:
    """Timing data aggregated by language."""

    language: str
    files: int = 0
    total_ms: float = 0.0
    avg_ms: float = 0.0
    total_lines: int = 0
    total_findings: int = 0

    def to_dict(self) -> dict[str, Any]:
        return {
            "language": self.language,
            "files": self.files,
            "total_ms": round(self.total_ms, 2),
            "avg_ms": round(self.avg_ms, 2),
            "total_lines": self.total_lines,
            "total_findings": self.total_findings,
        }


@dataclass
class MemoryProfile:
    """Basic memory profiling data."""

    rss_before_kb: int = 0
    rss_after_kb: int = 0
    rss_peak_kb: int = 0
    rss_delta_kb: int = 0

    def to_dict(self) -> dict[str, Any]:
        return {
            "rss_before_kb": self.rss_before_kb,
            "rss_after_kb": self.rss_after_kb,
            "rss_peak_kb": self.rss_peak_kb,
            "rss_delta_kb": self.rss_delta_kb,
        }


@dataclass
class BenchmarkResult:
    """Complete benchmark report."""

    iterations: int = 1
    total_ms: float = 0.0
    avg_iteration_ms: float = 0.0
    files: list[FileTiming] = field(default_factory=list)
    rules: list[RuleTiming] = field(default_factory=list)
    languages: list[LanguageTiming] = field(default_factory=list)
    memory: MemoryProfile = field(default_factory=MemoryProfile)
    slow_rules: list[RuleTiming] = field(default_factory=list)
    percentiles: dict[str, float] = field(default_factory=dict)
    target: str = ""

    def to_dict(self) -> dict[str, Any]:
        return {
            "iterations": self.iterations,
            "total_ms": round(self.total_ms, 2),
            "avg_iteration_ms": round(self.avg_iteration_ms, 2),
            "files_scanned": len(self.files),
            "files": [f.to_dict() for f in self.files],
            "rules": [r.to_dict() for r in self.rules],
            "languages": [lang.to_dict() for lang in self.languages],
            "memory": self.memory.to_dict(),
            "slow_rules": [r.to_dict() for r in self.slow_rules],
            "percentiles": {k: round(v, 2) for k, v in self.percentiles.items()},
            "target": self.target,
        }


# ---------------------------------------------------------------------------
# Memory helpers
# ---------------------------------------------------------------------------

def _get_rss_kb() -> int:
    """Get current RSS memory usage in KB."""
    try:
        usage = resource.getrusage(resource.RUSAGE_SELF)
        # On macOS ru_maxrss is in bytes, on Linux in KB
        import sys
        if sys.platform == "darwin":
            return usage.ru_maxrss // 1024
        return usage.ru_maxrss
    except Exception:
        return 0


def _get_current_rss_kb() -> int:
    """Get current RSS from /proc/self/status or fallback."""
    try:
        proc_status = Path("/proc/self/status")
        if proc_status.exists():
            for line in proc_status.read_text().splitlines():
                if line.startswith("VmRSS:"):
                    parts = line.split()
                    return int(parts[1])
    except Exception:
        pass
    return _get_rss_kb()


# ---------------------------------------------------------------------------
# Benchmark runner
# ---------------------------------------------------------------------------

def _detect_language(filepath: Path) -> str:
    """Detect language from file extension."""
    ext_map = {
        ".py": "python",
        ".ts": "typescript",
        ".tsx": "typescript",
        ".js": "javascript",
        ".jsx": "javascript",
        ".go": "go",
        ".json": "json",
        ".yaml": "yaml",
        ".yml": "yaml",
    }
    return ext_map.get(filepath.suffix.lower(), "unknown")


def _collect_files(target: Path) -> list[Path]:
    """Collect scannable files from a path."""
    scan_extensions = {".py", ".ts", ".tsx", ".js", ".jsx", ".go"}
    if target.is_file():
        return [target]
    files: list[Path] = []
    for fp in sorted(target.rglob("*")):
        if fp.is_file() and fp.suffix in scan_extensions:
            files.append(fp)
    return files


def run_benchmark(
    target: str,
    iterations: int = 10,
) -> BenchmarkResult:
    """Run performance benchmark on the scanner.

    Args:
        target: Path to file or directory to benchmark.
        iterations: Number of scan iterations.

    Returns:
        BenchmarkResult with all timing data.
    """
    # Import scanner lazily to avoid circular imports
    from .scanner import MCPSecurityScanner
    from .rules import get_all_rules

    target_path = Path(target)
    files = _collect_files(target_path)

    if not files:
        return BenchmarkResult(
            iterations=iterations,
            target=target,
        )

    rules = get_all_rules()
    scanner = MCPSecurityScanner()

    # Memory baseline
    rss_before = _get_current_rss_kb()

    # Per-file timing (single iteration)
    file_timings: list[FileTiming] = []
    rule_times: dict[str, list[float]] = {r.rule_id: [] for r in rules}
    iteration_times: list[float] = []

    for iteration in range(iterations):
        iter_start = time.perf_counter()

        for fp in files:
            file_start = time.perf_counter()

            try:
                source = fp.read_text(encoding="utf-8", errors="replace")
                lines_count = source.count("\n") + 1
            except (OSError, UnicodeDecodeError):
                continue

            summary = scanner.scan_file(fp)
            file_end = time.perf_counter()
            file_ms = (file_end - file_start) * 1000

            findings_count = sum(
                len(r.findings) for r in summary.results
            ) if hasattr(summary, "results") else 0

            if iteration == 0:
                file_timings.append(FileTiming(
                    file=str(fp),
                    language=_detect_language(fp),
                    scan_ms=file_ms,
                    lines=lines_count,
                    findings=findings_count,
                ))

            # Per-rule timing (sample on first iteration)
            if iteration == 0:
                for rule in rules:
                    rule_start = time.perf_counter()
                    try:
                        rule.check(source, str(fp))
                    except Exception:
                        pass
                    rule_end = time.perf_counter()
                    rule_ms = (rule_end - rule_start) * 1000
                    rule_times[rule.rule_id].append(rule_ms)

        iter_end = time.perf_counter()
        iteration_times.append((iter_end - iter_start) * 1000)

    # Memory after
    rss_after = _get_current_rss_kb()
    rss_peak = _get_rss_kb()

    # Compute rule timing stats
    rule_timings: list[RuleTiming] = []
    for rule in rules:
        times = rule_times.get(rule.rule_id, [])
        if times:
            avg = statistics.mean(times)
            rt = RuleTiming(
                rule_id=rule.rule_id,
                rule_name=rule.name,
                total_ms=sum(times),
                files_checked=len(times),
                avg_ms=avg,
                max_ms=max(times),
                min_ms=min(times),
                is_slow=avg > 100.0,
            )
            rule_timings.append(rt)

    slow_rules = [r for r in rule_timings if r.is_slow]

    # Language aggregation
    lang_data: dict[str, LanguageTiming] = {}
    for ft in file_timings:
        lang = ft.language
        if lang not in lang_data:
            lang_data[lang] = LanguageTiming(language=lang)
        lt = lang_data[lang]
        lt.files += 1
        lt.total_ms += ft.scan_ms
        lt.total_lines += ft.lines
        lt.total_findings += ft.findings
    for lt in lang_data.values():
        lt.avg_ms = lt.total_ms / lt.files if lt.files else 0.0

    # Percentiles
    file_times = [ft.scan_ms for ft in file_timings]
    percentiles: dict[str, float] = {}
    if file_times:
        sorted_times = sorted(file_times)
        percentiles["p50"] = _percentile(sorted_times, 50)
        percentiles["p75"] = _percentile(sorted_times, 75)
        percentiles["p90"] = _percentile(sorted_times, 90)
        percentiles["p95"] = _percentile(sorted_times, 95)
        percentiles["p99"] = _percentile(sorted_times, 99)
        percentiles["min"] = sorted_times[0]
        percentiles["max"] = sorted_times[-1]

    total_ms = sum(iteration_times)
    avg_ms = total_ms / iterations if iterations else 0.0

    return BenchmarkResult(
        iterations=iterations,
        total_ms=total_ms,
        avg_iteration_ms=avg_ms,
        files=file_timings,
        rules=rule_timings,
        languages=list(lang_data.values()),
        memory=MemoryProfile(
            rss_before_kb=rss_before,
            rss_after_kb=rss_after,
            rss_peak_kb=rss_peak,
            rss_delta_kb=rss_after - rss_before,
        ),
        slow_rules=slow_rules,
        percentiles=percentiles,
        target=target,
    )


def _percentile(sorted_data: list[float], p: int) -> float:
    """Compute percentile from pre-sorted data."""
    if not sorted_data:
        return 0.0
    n = len(sorted_data)
    k = (n - 1) * p / 100
    f = int(k)
    c = f + 1 if f + 1 < n else f
    d = k - f
    return sorted_data[f] + d * (sorted_data[c] - sorted_data[f])


# ---------------------------------------------------------------------------
# Output formatters
# ---------------------------------------------------------------------------

def format_benchmark_text(result: BenchmarkResult) -> str:
    """Format benchmark result as human-readable text.

    Args:
        result: BenchmarkResult to format.

    Returns:
        Formatted text output.
    """
    lines: list[str] = []
    lines.append("")
    lines.append("=" * 70)
    lines.append("  MCP Benchmark Runner — Performance Report")
    lines.append("=" * 70)
    lines.append(f"  Target: {result.target}")
    lines.append(f"  Iterations: {result.iterations}")
    lines.append(f"  Total time: {result.total_ms:.1f}ms")
    lines.append(f"  Avg iteration: {result.avg_iteration_ms:.1f}ms")
    lines.append(f"  Files scanned: {len(result.files)}")
    lines.append("=" * 70)

    # Percentiles
    if result.percentiles:
        lines.append("")
        lines.append("  File Scan Time Percentiles:")
        for key in ["min", "p50", "p75", "p90", "p95", "p99", "max"]:
            if key in result.percentiles:
                lines.append(f"    {key:>4}: {result.percentiles[key]:.2f}ms")

    # Language breakdown
    if result.languages:
        lines.append("")
        lines.append("  By Language:")
        lines.append(f"    {'Language':<15} {'Files':>6} {'Total ms':>10} {'Avg ms':>10} {'Lines':>8}")
        lines.append(f"    {'-'*15} {'-'*6} {'-'*10} {'-'*10} {'-'*8}")
        for lt in sorted(result.languages, key=lambda x: x.total_ms, reverse=True):
            lines.append(
                f"    {lt.language:<15} {lt.files:>6} "
                f"{lt.total_ms:>10.1f} {lt.avg_ms:>10.1f} {lt.total_lines:>8}"
            )

    # Top 10 slowest files
    if result.files:
        lines.append("")
        lines.append("  Top 10 Slowest Files:")
        slowest = sorted(result.files, key=lambda f: f.scan_ms, reverse=True)[:10]
        for ft in slowest:
            lines.append(f"    {ft.scan_ms:>8.1f}ms  {ft.file} ({ft.lines} lines)")

    # Slow rules
    if result.slow_rules:
        lines.append("")
        lines.append("  SLOW RULES (>100ms/file avg):")
        for rt in sorted(result.slow_rules, key=lambda r: r.avg_ms, reverse=True):
            lines.append(
                f"    [!] {rt.rule_id} — {rt.rule_name}: "
                f"avg={rt.avg_ms:.1f}ms, max={rt.max_ms:.1f}ms"
            )

    # Memory
    lines.append("")
    lines.append("  Memory Profile:")
    lines.append(f"    RSS before: {result.memory.rss_before_kb:,} KB")
    lines.append(f"    RSS after:  {result.memory.rss_after_kb:,} KB")
    lines.append(f"    RSS peak:   {result.memory.rss_peak_kb:,} KB")
    lines.append(f"    RSS delta:  {result.memory.rss_delta_kb:+,} KB")

    lines.append("")
    lines.append("=" * 70)
    return "\n".join(lines)


def format_benchmark_json(result: BenchmarkResult) -> str:
    """Format benchmark result as JSON.

    Args:
        result: BenchmarkResult to format.

    Returns:
        JSON string.
    """
    return json.dumps({"benchmark": result.to_dict()}, indent=2, ensure_ascii=False)


def format_benchmark_html(result: BenchmarkResult) -> str:
    """Format benchmark result as self-contained HTML report.

    Args:
        result: BenchmarkResult to format.

    Returns:
        Complete HTML string.
    """
    import html as html_mod

    # Build file rows
    file_rows = ""
    for ft in sorted(result.files, key=lambda f: f.scan_ms, reverse=True)[:20]:
        color = "#dc2626" if ft.scan_ms > 500 else (
            "#f97316" if ft.scan_ms > 100 else "#22c55e"
        )
        file_rows += (
            f"<tr><td style='color:{color};font-weight:bold;'>"
            f"{ft.scan_ms:.1f}ms</td>"
            f"<td>{html_mod.escape(ft.file)}</td>"
            f"<td>{ft.language}</td>"
            f"<td>{ft.lines}</td>"
            f"<td>{ft.findings}</td></tr>\n"
        )

    # Build rule rows
    rule_rows = ""
    for rt in sorted(result.rules, key=lambda r: r.avg_ms, reverse=True)[:20]:
        slow_marker = " [SLOW]" if rt.is_slow else ""
        color = "#dc2626" if rt.is_slow else "#22c55e"
        rule_rows += (
            f"<tr><td><code>{html_mod.escape(rt.rule_id)}</code></td>"
            f"<td>{html_mod.escape(rt.rule_name)}</td>"
            f"<td style='color:{color};'>{rt.avg_ms:.1f}ms{slow_marker}</td>"
            f"<td>{rt.max_ms:.1f}ms</td>"
            f"<td>{rt.files_checked}</td></tr>\n"
        )

    # Percentile bars
    pct_html = ""
    if result.percentiles:
        max_val = result.percentiles.get("max", 1.0) or 1.0
        for key in ["p50", "p75", "p90", "p95", "p99"]:
            val = result.percentiles.get(key, 0.0)
            width = min(100, int((val / max_val) * 100))
            pct_html += (
                f"<div style='margin:4px 0;'>"
                f"<span style='display:inline-block;width:40px;'>{key}</span>"
                f"<span style='display:inline-block;width:{width}%;max-width:300px;"
                f"background:#38bdf8;height:18px;border-radius:3px;'></span>"
                f" <span>{val:.1f}ms</span></div>\n"
            )

    return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>MCP Benchmark Report</title>
<style>
  * {{ margin: 0; padding: 0; box-sizing: border-box; }}
  body {{
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background: #0f172a; color: #e2e8f0; padding: 2em;
  }}
  h1 {{ color: #38bdf8; text-align: center; margin-bottom: 0.5em; }}
  .meta {{ text-align: center; color: #64748b; margin-bottom: 2em; }}
  .card {{
    background: #1e293b; border-radius: 12px; padding: 1.5em;
    border: 1px solid #334155; margin-bottom: 1.5em;
  }}
  .card h2 {{ color: #94a3b8; font-size: 1em; margin-bottom: 1em;
    border-bottom: 1px solid #334155; padding-bottom: 0.5em; }}
  table {{ width: 100%; border-collapse: collapse; }}
  th, td {{ text-align: left; padding: 6px 10px; border-bottom: 1px solid #334155; }}
  th {{ color: #64748b; font-weight: 600; font-size: 0.85em; }}
  td {{ font-size: 0.9em; }}
  .stats {{ display: flex; gap: 2em; justify-content: center; margin: 1em 0; flex-wrap: wrap; }}
  .stat {{ text-align: center; }}
  .stat-val {{ font-size: 1.5em; font-weight: bold; color: #38bdf8; }}
  .stat-lbl {{ color: #64748b; font-size: 0.8em; }}
  .footer {{ text-align: center; color: #475569; font-size: 0.8em; margin-top: 2em; }}
</style>
</head>
<body>
<h1>MCP Benchmark Report</h1>
<div class="meta">Target: {html_mod.escape(result.target)} | {result.iterations} iterations</div>

<div class="stats">
  <div class="stat"><div class="stat-val">{result.total_ms:.0f}ms</div><div class="stat-lbl">Total</div></div>
  <div class="stat"><div class="stat-val">{result.avg_iteration_ms:.0f}ms</div><div class="stat-lbl">Avg/Iter</div></div>
  <div class="stat"><div class="stat-val">{len(result.files)}</div><div class="stat-lbl">Files</div></div>
  <div class="stat"><div class="stat-val">{len(result.slow_rules)}</div><div class="stat-lbl">Slow Rules</div></div>
  <div class="stat"><div class="stat-val">{result.memory.rss_delta_kb:+,} KB</div><div class="stat-lbl">RSS Delta</div></div>
</div>

<div class="card">
  <h2>File Scan Time Percentiles</h2>
  {pct_html}
</div>

<div class="card">
  <h2>Top Files by Scan Time</h2>
  <table>
    <tr><th>Time</th><th>File</th><th>Language</th><th>Lines</th><th>Findings</th></tr>
    {file_rows}
  </table>
</div>

<div class="card">
  <h2>Rule Performance</h2>
  <table>
    <tr><th>Rule ID</th><th>Name</th><th>Avg Time</th><th>Max Time</th><th>Files</th></tr>
    {rule_rows}
  </table>
</div>

<div class="footer">Generated by mcpaudit benchmark</div>
</body>
</html>"""
