"""Tests for summary report — aggregate stats and executive summaries."""

from __future__ import annotations

import json
import textwrap
from pathlib import Path

import pytest

from mcpaudit.models import Finding, ScanResult, ScanSummary, Severity
from mcpaudit.summary import (
    ExecutiveSummary,
    SummaryReport,
    build_summary,
    format_summary_report_json,
    format_summary_report_text,
    top_findings,
)


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def multi_scan_results() -> list[ScanResult]:
    """Multiple scan results with overlapping findings."""
    return [
        ScanResult(
            file="server_a.py",
            findings=[
                Finding(
                    rule_id="CMD-001", severity=Severity.CRITICAL,
                    title="Command injection", description="uses shell=True",
                    file="server_a.py", line=10,
                ),
                Finding(
                    rule_id="AUTH-001", severity=Severity.HIGH,
                    title="Missing auth", description="No auth check",
                    file="server_a.py", line=20,
                ),
                Finding(
                    rule_id="CMD-001", severity=Severity.CRITICAL,
                    title="Command injection", description="unsafe command exec",
                    file="server_a.py", line=30,
                ),
            ],
            lines_scanned=200,
        ),
        ScanResult(
            file="server_b.py",
            findings=[
                Finding(
                    rule_id="AUTH-001", severity=Severity.HIGH,
                    title="Missing auth", description="No auth check",
                    file="server_b.py", line=5,
                ),
                Finding(
                    rule_id="LOG-001", severity=Severity.LOW,
                    title="Missing logging", description="No log calls",
                    file="server_b.py", line=15,
                ),
            ],
            lines_scanned=150,
        ),
        ScanResult(
            file="server_c.py",
            findings=[],
            lines_scanned=100,
        ),
    ]


@pytest.fixture
def empty_results() -> list[ScanResult]:
    """No scan results."""
    return []


@pytest.fixture
def history_entries() -> list[dict]:
    """Mock history entries for trend comparison."""
    return [
        {
            "timestamp": "20260315T120000Z",
            "label": "previous",
            "scan": {
                "summary": {
                    "total_findings": 10,
                    "overall_grade": "C",
                    "severity_counts": {
                        "CRITICAL": 2, "HIGH": 3,
                        "MEDIUM": 3, "LOW": 2, "INFO": 0,
                    },
                },
            },
        },
        {
            "timestamp": "20260318T120000Z",
            "label": "current",
            "scan": {
                "summary": {
                    "total_findings": 5,
                    "overall_grade": "B",
                    "severity_counts": {
                        "CRITICAL": 0, "HIGH": 2,
                        "MEDIUM": 2, "LOW": 1, "INFO": 0,
                    },
                },
            },
        },
    ]


# ---------------------------------------------------------------------------
# top_findings tests
# ---------------------------------------------------------------------------

class TestTopFindings:
    """Tests for top findings aggregation."""

    def test_counts_by_rule_id(self, multi_scan_results: list[ScanResult]) -> None:
        top = top_findings(multi_scan_results)
        # CMD-001 appears 2 times, AUTH-001 appears 2 times
        assert top[0][0] in ("CMD-001", "AUTH-001")
        assert top[0][1] >= 2

    def test_returns_sorted_desc(self, multi_scan_results: list[ScanResult]) -> None:
        top = top_findings(multi_scan_results)
        counts = [t[1] for t in top]
        assert counts == sorted(counts, reverse=True)

    def test_limit_works(self, multi_scan_results: list[ScanResult]) -> None:
        top = top_findings(multi_scan_results, limit=2)
        assert len(top) <= 2

    def test_empty_results(self, empty_results: list[ScanResult]) -> None:
        top = top_findings(empty_results)
        assert top == []

    def test_default_limit_is_10(self, multi_scan_results: list[ScanResult]) -> None:
        top = top_findings(multi_scan_results)
        assert len(top) <= 10


# ---------------------------------------------------------------------------
# build_summary tests
# ---------------------------------------------------------------------------

class TestBuildSummary:
    """Tests for building summary reports."""

    def test_returns_summary_report(self, multi_scan_results: list[ScanResult]) -> None:
        report = build_summary(multi_scan_results)
        assert isinstance(report, SummaryReport)

    def test_total_files(self, multi_scan_results: list[ScanResult]) -> None:
        report = build_summary(multi_scan_results)
        assert report.total_files == 3

    def test_total_findings(self, multi_scan_results: list[ScanResult]) -> None:
        report = build_summary(multi_scan_results)
        assert report.total_findings == 5

    def test_total_lines(self, multi_scan_results: list[ScanResult]) -> None:
        report = build_summary(multi_scan_results)
        assert report.total_lines == 450

    def test_severity_counts(self, multi_scan_results: list[ScanResult]) -> None:
        report = build_summary(multi_scan_results)
        assert report.severity_counts["CRITICAL"] == 2
        assert report.severity_counts["HIGH"] == 2
        assert report.severity_counts["LOW"] == 1

    def test_top_findings_present(self, multi_scan_results: list[ScanResult]) -> None:
        report = build_summary(multi_scan_results)
        assert len(report.top_findings) > 0

    def test_executive_summary(self, multi_scan_results: list[ScanResult]) -> None:
        report = build_summary(multi_scan_results)
        assert isinstance(report.executive_summary, ExecutiveSummary)
        assert report.executive_summary.paragraph != ""

    def test_empty_results_summary(self, empty_results: list[ScanResult]) -> None:
        report = build_summary(empty_results)
        assert report.total_files == 0
        assert report.total_findings == 0

    def test_with_history(self, multi_scan_results: list[ScanResult], history_entries: list[dict]) -> None:
        report = build_summary(multi_scan_results, history=history_entries)
        assert report.trend is not None

    def test_trend_improving(self, multi_scan_results: list[ScanResult], history_entries: list[dict]) -> None:
        report = build_summary(multi_scan_results, history=history_entries)
        # Previous had 10 findings, current scan has 5 — improving
        assert report.trend is not None

    def test_to_dict(self, multi_scan_results: list[ScanResult]) -> None:
        report = build_summary(multi_scan_results)
        d = report.to_dict()
        assert "total_files" in d
        assert "total_findings" in d
        assert "severity_counts" in d
        assert "top_findings" in d
        assert "executive_summary" in d

    def test_overall_grade(self, multi_scan_results: list[ScanResult]) -> None:
        report = build_summary(multi_scan_results)
        assert report.overall_grade in ("A+", "A", "B", "C", "D", "F")


# ---------------------------------------------------------------------------
# Formatting tests
# ---------------------------------------------------------------------------

class TestSummaryFormatting:
    """Tests for summary report formatting."""

    def test_format_text_header(self, multi_scan_results: list[ScanResult]) -> None:
        report = build_summary(multi_scan_results)
        text = format_summary_report_text(report)
        assert "Summary Report" in text

    def test_format_text_executive(self, multi_scan_results: list[ScanResult]) -> None:
        report = build_summary(multi_scan_results)
        text = format_summary_report_text(report)
        assert "Executive Summary" in text

    def test_format_text_top_findings(self, multi_scan_results: list[ScanResult]) -> None:
        report = build_summary(multi_scan_results)
        text = format_summary_report_text(report)
        assert "Top Findings" in text

    def test_format_json_valid(self, multi_scan_results: list[ScanResult]) -> None:
        report = build_summary(multi_scan_results)
        data = json.loads(format_summary_report_json(report))
        assert "total_files" in data

    def test_format_text_empty(self, empty_results: list[ScanResult]) -> None:
        report = build_summary(empty_results)
        text = format_summary_report_text(report)
        assert "No scan results" in text or "0 files" in text

    def test_format_text_with_trend(self, multi_scan_results: list[ScanResult], history_entries: list[dict]) -> None:
        report = build_summary(multi_scan_results, history=history_entries)
        text = format_summary_report_text(report)
        assert "Trend" in text or "trend" in text

    def test_format_json_with_trend(self, multi_scan_results: list[ScanResult], history_entries: list[dict]) -> None:
        report = build_summary(multi_scan_results, history=history_entries)
        data = json.loads(format_summary_report_json(report))
        assert "trend" in data
