"""Tests for scan history management and dashboard generation."""

from __future__ import annotations

import json
import tempfile
from pathlib import Path

import pytest

from mcpaudit.history import (
    compute_trends,
    format_history_text,
    generate_dashboard_html,
    load_history,
    save_scan,
)


def _make_summary(
    grade: str = "B",
    findings: int = 5,
    critical: int = 0,
    high: int = 1,
    medium: int = 2,
    low: int = 2,
    files: int = 3,
) -> dict:
    """Create a mock scan summary dict."""
    return {
        "summary": {
            "files_scanned": files,
            "lines_scanned": 100,
            "total_findings": findings,
            "overall_grade": grade,
            "severity_counts": {
                "CRITICAL": critical,
                "HIGH": high,
                "MEDIUM": medium,
                "LOW": low,
                "INFO": 0,
            },
            "scan_time_ms": 12.5,
            "exit_code": 2 if critical else (1 if high else 0),
        },
        "results": [],
    }


# --- save_scan ---

class TestSaveScan:
    def test_saves_json_file(self, tmp_path: Path) -> None:
        summary = _make_summary()
        result = save_scan(summary, history_dir=tmp_path)
        assert result.exists()
        assert result.suffix == ".json"
        data = json.loads(result.read_text())
        assert "timestamp" in data
        assert "scan" in data

    def test_saves_with_label(self, tmp_path: Path) -> None:
        result = save_scan(_make_summary(), history_dir=tmp_path, label="ci-main")
        data = json.loads(result.read_text())
        assert data["label"] == "ci-main"

    def test_creates_history_dir(self, tmp_path: Path) -> None:
        history_dir = tmp_path / "subdir" / "history"
        save_scan(_make_summary(), history_dir=history_dir)
        assert history_dir.exists()

    def test_filename_contains_scan_prefix(self, tmp_path: Path) -> None:
        result = save_scan(_make_summary(), history_dir=tmp_path)
        assert result.name.startswith("scan-")

    def test_multiple_saves_create_unique_files(self, tmp_path: Path) -> None:
        f1 = save_scan(_make_summary(), history_dir=tmp_path)
        f2 = save_scan(_make_summary(), history_dir=tmp_path)
        # Files may have same name if saved in same second, but both exist
        assert f1.exists()
        assert f2.exists()

    def test_preserves_summary_data(self, tmp_path: Path) -> None:
        summary = _make_summary(grade="F", findings=20, critical=3)
        result = save_scan(summary, history_dir=tmp_path)
        data = json.loads(result.read_text())
        assert data["scan"]["summary"]["overall_grade"] == "F"
        assert data["scan"]["summary"]["total_findings"] == 20
        assert data["scan"]["summary"]["severity_counts"]["CRITICAL"] == 3


# --- load_history ---

class TestLoadHistory:
    def test_empty_directory(self, tmp_path: Path) -> None:
        entries = load_history(tmp_path)
        assert entries == []

    def test_nonexistent_directory(self, tmp_path: Path) -> None:
        entries = load_history(tmp_path / "nonexistent")
        assert entries == []

    def test_loads_saved_entries(self, tmp_path: Path) -> None:
        save_scan(_make_summary(grade="A"), history_dir=tmp_path)
        save_scan(_make_summary(grade="B"), history_dir=tmp_path)
        entries = load_history(tmp_path)
        assert len(entries) >= 1  # May be 1 or 2 depending on timing

    def test_sorted_by_timestamp(self, tmp_path: Path) -> None:
        # Create files with different timestamps manually
        for i, grade in enumerate(["C", "B", "A"]):
            entry = {
                "timestamp": f"2026010{i+1}T120000Z",
                "label": "",
                "scan": _make_summary(grade=grade),
            }
            (tmp_path / f"scan-2026010{i+1}T120000Z.json").write_text(
                json.dumps(entry)
            )
        entries = load_history(tmp_path)
        assert len(entries) == 3
        assert entries[0]["timestamp"] == "20260101T120000Z"
        assert entries[2]["timestamp"] == "20260103T120000Z"

    def test_limit_parameter(self, tmp_path: Path) -> None:
        for i in range(5):
            entry = {
                "timestamp": f"2026010{i+1}T120000Z",
                "label": "",
                "scan": _make_summary(),
            }
            (tmp_path / f"scan-2026010{i+1}T120000Z.json").write_text(
                json.dumps(entry)
            )
        entries = load_history(tmp_path, limit=2)
        assert len(entries) == 2
        # Should return the LAST 2 entries
        assert entries[0]["timestamp"] == "20260104T120000Z"

    def test_ignores_invalid_json(self, tmp_path: Path) -> None:
        (tmp_path / "scan-invalid.json").write_text("not json{{{")
        save_scan(_make_summary(), history_dir=tmp_path)
        entries = load_history(tmp_path)
        assert len(entries) >= 1

    def test_ignores_non_scan_files(self, tmp_path: Path) -> None:
        (tmp_path / "readme.txt").write_text("hello")
        (tmp_path / "config.json").write_text("{}")
        entries = load_history(tmp_path)
        assert entries == []


# --- compute_trends ---

class TestComputeTrends:
    def test_empty_entries(self) -> None:
        trends = compute_trends([])
        assert trends["entries"] == 0
        assert trends["trend"] == "no_data"

    def test_single_entry_is_baseline(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "",
             "scan": _make_summary(grade="B", findings=5)}
        ]
        trends = compute_trends(entries)
        assert trends["entries"] == 1
        assert trends["trend"] == "baseline"
        assert trends["findings_delta"] == 0
        assert trends["latest"]["grade"] == "B"

    def test_improving_trend(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "",
             "scan": _make_summary(grade="D", findings=15)},
            {"timestamp": "20260102T120000Z", "label": "",
             "scan": _make_summary(grade="B", findings=5)},
        ]
        trends = compute_trends(entries)
        assert trends["trend"] == "improving"
        assert trends["findings_delta"] == -10

    def test_degrading_trend(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "",
             "scan": _make_summary(grade="A", findings=1)},
            {"timestamp": "20260102T120000Z", "label": "",
             "scan": _make_summary(grade="D", findings=15)},
        ]
        trends = compute_trends(entries)
        assert trends["trend"] == "degrading"
        assert trends["findings_delta"] == 14

    def test_stable_trend(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "",
             "scan": _make_summary(grade="B", findings=5)},
            {"timestamp": "20260102T120000Z", "label": "",
             "scan": _make_summary(grade="B", findings=3)},
        ]
        trends = compute_trends(entries)
        assert trends["trend"] == "stable"
        assert trends["findings_delta"] == -2

    def test_points_include_all_entries(self) -> None:
        entries = [
            {"timestamp": f"2026010{i}T120000Z", "label": "",
             "scan": _make_summary(grade="B")}
            for i in range(1, 6)
        ]
        trends = compute_trends(entries)
        assert len(trends["points"]) == 5

    def test_severity_breakdown_in_points(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "",
             "scan": _make_summary(critical=2, high=3, medium=1, low=0)}
        ]
        trends = compute_trends(entries)
        point = trends["points"][0]
        assert point["critical"] == 2
        assert point["high"] == 3
        assert point["medium"] == 1
        assert point["low"] == 0


# --- format_history_text ---

class TestFormatHistoryText:
    def test_no_history_message(self) -> None:
        result = format_history_text([])
        assert "No scan history" in result

    def test_includes_header(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "test",
             "scan": _make_summary(grade="B", findings=5)}
        ]
        result = format_history_text(entries)
        assert "History" in result
        assert "Total scans: 1" in result

    def test_includes_grade(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "",
             "scan": _make_summary(grade="A+", findings=0)}
        ]
        result = format_history_text(entries)
        assert "A+" in result

    def test_includes_trend(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "",
             "scan": _make_summary(grade="D")},
            {"timestamp": "20260102T120000Z", "label": "",
             "scan": _make_summary(grade="A")},
        ]
        result = format_history_text(entries)
        assert "IMPROVING" in result

    def test_includes_delta(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "",
             "scan": _make_summary(findings=10)},
            {"timestamp": "20260102T120000Z", "label": "",
             "scan": _make_summary(findings=3)},
        ]
        result = format_history_text(entries)
        assert "-7" in result

    def test_includes_label(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "ci-main",
             "scan": _make_summary()}
        ]
        result = format_history_text(entries)
        assert "ci-main" in result


# --- generate_dashboard_html ---

class TestGenerateDashboardHtml:
    def test_returns_valid_html(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "",
             "scan": _make_summary(grade="B")}
        ]
        html = generate_dashboard_html(entries)
        assert html.startswith("<!DOCTYPE html>")
        assert "</html>" in html

    def test_includes_grade(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "",
             "scan": _make_summary(grade="A+")}
        ]
        html = generate_dashboard_html(entries)
        assert "A+" in html

    def test_includes_chart_data(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "",
             "scan": _make_summary(findings=5)},
            {"timestamp": "20260102T120000Z", "label": "",
             "scan": _make_summary(findings=3)},
        ]
        html = generate_dashboard_html(entries)
        assert "Chart" in html
        assert "findingsChart" in html

    def test_includes_trend_label(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "",
             "scan": _make_summary(grade="D")},
            {"timestamp": "20260102T120000Z", "label": "",
             "scan": _make_summary(grade="A")},
        ]
        html = generate_dashboard_html(entries)
        assert "IMPROVING" in html

    def test_empty_entries_produces_html(self) -> None:
        html = generate_dashboard_html([])
        assert "<!DOCTYPE html>" in html

    def test_includes_table_rows(self) -> None:
        entries = [
            {"timestamp": "20260101T120000Z", "label": "test",
             "scan": _make_summary(grade="C", critical=1, high=2)}
        ]
        html = generate_dashboard_html(entries)
        assert "<tr>" in html
        assert "test" in html

    def test_includes_carlos_footer(self) -> None:
        html = generate_dashboard_html([])
        assert "Carlos Miret" in html

    def test_chart_js_included(self) -> None:
        html = generate_dashboard_html([{"timestamp": "20260101T120000Z",
                                          "label": "", "scan": _make_summary()}])
        assert "chart.js" in html


# --- CLI integration ---

class TestHistoryCLI:
    def test_history_flag_no_data(self) -> None:
        from mcpaudit.__main__ import main
        with tempfile.TemporaryDirectory() as tmp:
            code = main(["--history", "--history-dir", tmp])
            assert code == 0

    def test_save_history_creates_file(self, tmp_path: Path) -> None:
        from mcpaudit.__main__ import main
        # Create a minimal Python file to scan
        target = tmp_path / "server.py"
        target.write_text("import os\nprint('hello')\n")
        history_dir = tmp_path / "history"
        code = main([
            str(target), "--save-history",
            "--history-dir", str(history_dir),
            "--history-label", "test-run",
            "--quiet",
        ])
        assert code == 0
        files = list(history_dir.glob("scan-*.json"))
        assert len(files) == 1
        data = json.loads(files[0].read_text())
        assert data["label"] == "test-run"

    def test_history_dashboard_generates_html(self, tmp_path: Path) -> None:
        from mcpaudit.__main__ import main
        # Create history manually
        history_dir = tmp_path / "history"
        history_dir.mkdir()
        entry = {
            "timestamp": "20260101T120000Z",
            "label": "test",
            "scan": _make_summary(grade="B"),
        }
        (history_dir / "scan-20260101T120000Z.json").write_text(json.dumps(entry))

        dashboard_path = tmp_path / "dashboard.html"
        code = main([
            "--history-dashboard", str(dashboard_path),
            "--history-dir", str(history_dir),
        ])
        assert code == 0
        assert dashboard_path.exists()
        html = dashboard_path.read_text()
        assert "<!DOCTYPE html>" in html
        assert "B" in html

    def test_save_then_show_history(self, tmp_path: Path) -> None:
        from mcpaudit.__main__ import main
        target = tmp_path / "server.py"
        target.write_text("x = 1\n")
        history_dir = tmp_path / "history"

        # Save
        main([str(target), "--save-history", "--history-dir", str(history_dir), "-q"])
        # Show
        code = main(["--history", "--history-dir", str(history_dir)])
        assert code == 0

    def test_history_with_label(self, tmp_path: Path) -> None:
        from mcpaudit.__main__ import main
        target = tmp_path / "server.py"
        target.write_text("pass\n")
        history_dir = tmp_path / "history"
        main([
            str(target), "--save-history",
            "--history-dir", str(history_dir),
            "--history-label", "v0.5.0-rc1",
            "-q",
        ])
        entries = load_history(history_dir)
        assert entries[0]["label"] == "v0.5.0-rc1"
