"""Tests for mcpaudit Fleet HTML Report (fleet_report.py).

Covers: generate_fleet_report, generate_fleet_report_from_dict,
HTML structure, executive summary, heatmap, server details,
recommendations, trend section, edge cases.
Minimum: 25 tests.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from mcpaudit.fleet_report import (
    generate_fleet_report,
    generate_fleet_report_from_dict,
    _grade_color,
    _severity_color,
    _heatmap_color,
    _compute_overall_grade,
)
from mcpaudit.models import Finding, ScanResult, Severity


# ===================================================================
# Helpers
# ===================================================================

def _make_finding(
    rule_id: str = "CMD-001",
    severity: Severity = Severity.HIGH,
    title: str = "Test finding",
) -> Finding:
    return Finding(
        rule_id=rule_id,
        severity=severity,
        title=title,
        description="Test description",
        file="test.py",
        line=10,
        code_snippet="subprocess.run(cmd, shell=True)",
        cwe="CWE-78",
    )


def _make_scan_result(
    file: str = "server.py",
    findings: list[Finding] | None = None,
    lines: int = 100,
) -> ScanResult:
    return ScanResult(
        file=file,
        findings=findings or [],
        lines_scanned=lines,
    )


# ===================================================================
# generate_fleet_report (list[ScanResult])
# ===================================================================

class TestGenerateFleetReport:
    def test_returns_html(self):
        results = [_make_scan_result()]
        html = generate_fleet_report(results)
        assert "<!DOCTYPE html>" in html
        assert "</html>" in html

    def test_empty_results(self):
        html = generate_fleet_report([])
        assert "<!DOCTYPE html>" in html
        assert "A+" in html  # No findings = A+

    def test_contains_executive_summary(self):
        results = [_make_scan_result(findings=[_make_finding()])]
        html = generate_fleet_report(results)
        assert "Executive Summary" in html

    def test_contains_heatmap(self):
        results = [_make_scan_result(findings=[_make_finding()])]
        html = generate_fleet_report(results)
        assert "Risk Heatmap" in html

    def test_contains_server_details(self):
        results = [_make_scan_result(file="my_server.py")]
        html = generate_fleet_report(results)
        assert "Per-Server Detail" in html
        assert "my_server.py" in html

    def test_contains_recommendations(self):
        results = [
            _make_scan_result(findings=[
                _make_finding(severity=Severity.CRITICAL),
            ]),
        ]
        html = generate_fleet_report(results)
        assert "Top Recommendations" in html
        assert "CRITICAL" in html

    def test_multiple_servers(self):
        results = [
            _make_scan_result(file="server_a.py", findings=[_make_finding()]),
            _make_scan_result(file="server_b.py"),
        ]
        html = generate_fleet_report(results)
        assert "server_a.py" in html
        assert "server_b.py" in html

    def test_severity_distribution(self):
        results = [
            _make_scan_result(findings=[
                _make_finding(severity=Severity.CRITICAL),
                _make_finding(rule_id="PATH-001", severity=Severity.MEDIUM),
            ]),
        ]
        html = generate_fleet_report(results)
        assert "Severity Distribution" in html

    def test_trend_section_with_history(self):
        results = [_make_scan_result()]
        history = [
            {
                "timestamp": "2024-01-01",
                "scan": {
                    "summary": {
                        "overall_grade": "B",
                        "total_findings": 5,
                        "files_scanned": 3,
                    }
                },
            }
        ]
        html = generate_fleet_report(results, history=history)
        assert "Trend" in html

    def test_no_trend_without_history(self):
        results = [_make_scan_result()]
        html = generate_fleet_report(results, history=None)
        # Should not have trend section (or empty)
        assert "<!DOCTYPE html>" in html

    def test_clean_scan_shows_no_findings(self):
        results = [_make_scan_result()]
        html = generate_fleet_report(results)
        assert "No findings" in html or "clean scan" in html

    def test_html_escapes_filenames(self):
        results = [_make_scan_result(file="<script>alert(1)</script>.py")]
        html = generate_fleet_report(results)
        assert "<script>alert(1)</script>" not in html
        assert "&lt;script&gt;" in html


# ===================================================================
# generate_fleet_report_from_dict
# ===================================================================

class TestGenerateFleetReportFromDict:
    def test_basic_dict(self):
        data = {
            "total_servers": 1,
            "total_findings": 2,
            "fleet_grade": "B",
            "severity_counts": {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 1,
                                "LOW": 0, "INFO": 0},
            "servers": [
                {
                    "file": "server.py",
                    "grade": "B",
                    "findings_count": 2,
                    "scan_result": {
                        "findings": [
                            {
                                "rule_id": "CMD-001",
                                "severity": "HIGH",
                                "title": "Shell injection",
                                "description": "shell=True detected",
                                "file": "server.py",
                                "line": 10,
                            },
                            {
                                "rule_id": "PATH-001",
                                "severity": "MEDIUM",
                                "title": "Path traversal",
                                "description": "Unvalidated path",
                                "file": "server.py",
                                "line": 20,
                            },
                        ],
                        "lines_scanned": 50,
                    },
                }
            ],
        }
        html = generate_fleet_report_from_dict(data)
        assert "<!DOCTYPE html>" in html
        assert "server.py" in html

    def test_empty_dict(self):
        data = {"servers": []}
        html = generate_fleet_report_from_dict(data)
        assert "<!DOCTYPE html>" in html

    def test_writes_file(self, tmp_path: Path):
        data = {
            "servers": [
                {
                    "file": "test.py",
                    "grade": "A+",
                    "findings_count": 0,
                    "scan_result": {"findings": [], "lines_scanned": 10},
                }
            ],
        }
        output = str(tmp_path / "report.html")
        generate_fleet_report_from_dict(data, output_path=output)
        assert Path(output).exists()
        content = Path(output).read_text(encoding="utf-8")
        assert "<!DOCTYPE html>" in content

    def test_scan_result_as_object(self):
        """When scan_result is already a ScanResult object."""
        sr = _make_scan_result(file="obj_server.py")
        data = {
            "servers": [
                {
                    "file": "obj_server.py",
                    "grade": "A+",
                    "findings_count": 0,
                    "scan_result": sr,
                }
            ],
        }
        html = generate_fleet_report_from_dict(data)
        assert "obj_server.py" in html


# ===================================================================
# Helper functions
# ===================================================================

class TestHelpers:
    def test_grade_color_a_plus(self):
        assert _grade_color("A+") == "#22c55e"

    def test_grade_color_f(self):
        assert _grade_color("F") == "#dc2626"

    def test_grade_color_unknown(self):
        assert _grade_color("X") == "#6b7280"

    def test_severity_color_critical(self):
        assert _severity_color("CRITICAL") == "#dc2626"

    def test_severity_color_info(self):
        assert _severity_color("INFO") == "#6b7280"

    def test_heatmap_color_zero(self):
        assert _heatmap_color(0) == "#1e293b"

    def test_heatmap_color_high(self):
        assert _heatmap_color(15) == "#3a1e1e"

    def test_compute_overall_grade_empty(self):
        assert _compute_overall_grade([]) == "A+"

    def test_compute_overall_grade_worst(self):
        results = [
            _make_scan_result(findings=[
                _make_finding(severity=Severity.CRITICAL),
                _make_finding(severity=Severity.CRITICAL),
                _make_finding(severity=Severity.CRITICAL),
            ]),
            _make_scan_result(),
        ]
        grade = _compute_overall_grade(results)
        # Should be the worst grade
        assert grade in ("D", "F")
