"""Tests for the Security Trend Analyzer module."""

from __future__ import annotations

import json

import pytest

from mcpaudit.trend_analyzer import (
    RegressionEvent,
    ScanSnapshot,
    TrendAnalyzer,
    TrendPoint,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _snap(
    ts: str = "2026-01-01T00:00:00Z",
    total: int = 5,
    critical: int = 0,
    high: int = 1,
    medium: int = 2,
    low: int = 1,
    info: int = 1,
    score: int = 10,
    grade: str = "C",
    rules: tuple[str, ...] = ("SEC-001", "SEC-002"),
    label: str = "",
    files: int = 10,
) -> ScanSnapshot:
    return ScanSnapshot(
        timestamp=ts,
        total_findings=total,
        critical=critical,
        high=high,
        medium=medium,
        low=low,
        info=info,
        files_scanned=files,
        weighted_score=score,
        grade=grade,
        rule_ids=rules,
        label=label,
    )


# ===========================================================================
# ScanSnapshot
# ===========================================================================

class TestScanSnapshot:
    def test_defaults(self):
        s = ScanSnapshot(timestamp="2026-01-01T00:00:00Z", total_findings=0)
        assert s.critical == 0
        assert s.grade == "A+"
        assert s.rule_ids == ()

    def test_severity_counts(self):
        s = _snap(critical=1, high=2, medium=3, low=4, info=5)
        counts = s.severity_counts
        assert counts["CRITICAL"] == 1
        assert counts["HIGH"] == 2
        assert counts["MEDIUM"] == 3
        assert counts["LOW"] == 4
        assert counts["INFO"] == 5

    def test_to_dict(self):
        s = _snap(label="test")
        d = s.to_dict()
        assert d["timestamp"] == "2026-01-01T00:00:00Z"
        assert d["total_findings"] == 5
        assert d["label"] == "test"
        assert isinstance(d["rule_ids"], list)

    def test_from_dict(self):
        s = _snap(label="v1.0")
        d = s.to_dict()
        s2 = ScanSnapshot.from_dict(d)
        assert s2.timestamp == s.timestamp
        assert s2.total_findings == s.total_findings
        assert s2.label == "v1.0"
        assert s2.rule_ids == s.rule_ids

    def test_roundtrip(self):
        s = _snap(critical=3, high=5, rules=("A", "B", "C"))
        s2 = ScanSnapshot.from_dict(s.to_dict())
        assert s == s2

    def test_from_dict_missing_optionals(self):
        data = {"timestamp": "2026-01-01T00:00:00Z", "total_findings": 3}
        s = ScanSnapshot.from_dict(data)
        assert s.critical == 0
        assert s.grade == "A+"
        assert s.rule_ids == ()


# ===========================================================================
# TrendPoint
# ===========================================================================

class TestTrendPoint:
    def test_creation(self):
        p = TrendPoint(timestamp="2026-01-01T00:00:00Z", value=42.0)
        assert p.value == 42.0
        assert p.label == ""

    def test_with_label(self):
        p = TrendPoint(timestamp="2026-01-01T00:00:00Z", value=0.0, label="scan-1")
        assert p.label == "scan-1"


# ===========================================================================
# TrendAnalyzer - basic
# ===========================================================================

class TestTrendAnalyzerBasic:
    def test_empty(self):
        a = TrendAnalyzer()
        assert a.count == 0
        assert a.snapshots == []
        assert a.latest() is None
        assert a.earliest() is None

    def test_add_snapshot(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap())
        assert a.count == 1

    def test_chronological_order(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-03T00:00:00Z"))
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z"))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z"))
        assert a.snapshots[0].timestamp == "2026-01-01T00:00:00Z"
        assert a.snapshots[-1].timestamp == "2026-01-03T00:00:00Z"

    def test_latest_earliest(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z"))
        a.add_snapshot(_snap(ts="2026-01-03T00:00:00Z"))
        assert a.earliest().timestamp == "2026-01-01T00:00:00Z"
        assert a.latest().timestamp == "2026-01-03T00:00:00Z"


# ===========================================================================
# TrendAnalyzer - trends
# ===========================================================================

class TestTrendAnalyzerTrends:
    def test_findings_trend(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", total=10))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", total=5))
        trend = a.findings_trend()
        assert len(trend) == 2
        assert trend[0].value == 10.0
        assert trend[1].value == 5.0

    def test_score_trend(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", score=20))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", score=10))
        trend = a.score_trend()
        assert trend[0].value == 20.0
        assert trend[1].value == 10.0

    def test_grade_trend(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", grade="F"))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", grade="A+"))
        trend = a.grade_trend()
        assert trend[0].value == 1.0  # F
        assert trend[1].value == 6.0  # A+
        assert trend[1].label == "A+"

    def test_severity_trend(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", critical=5))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", critical=2))
        trend = a.severity_trend("CRITICAL")
        assert trend[0].value == 5.0
        assert trend[1].value == 2.0


# ===========================================================================
# TrendAnalyzer - analysis
# ===========================================================================

class TestTrendAnalyzerAnalysis:
    def test_improvement_rate_none(self):
        a = TrendAnalyzer()
        assert a.improvement_rate() is None

    def test_improvement_rate_single(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap())
        assert a.improvement_rate() is None

    def test_improvement_rate_improving(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", total=10))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", total=5))
        rate = a.improvement_rate()
        assert rate == -0.5  # 50% fewer findings

    def test_improvement_rate_degrading(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", total=5))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", total=10))
        rate = a.improvement_rate()
        assert rate == 1.0  # doubled

    def test_improvement_rate_zero_start(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", total=0))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", total=5))
        assert a.improvement_rate() == 0.0

    def test_risk_velocity_none(self):
        a = TrendAnalyzer()
        assert a.risk_velocity() is None

    def test_risk_velocity_increasing(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", total=5))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", total=10))
        a.add_snapshot(_snap(ts="2026-01-03T00:00:00Z", total=15))
        assert a.risk_velocity() == 5.0

    def test_risk_velocity_decreasing(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", total=10))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", total=5))
        assert a.risk_velocity() == 0.0  # no increases

    def test_most_common_rules_empty(self):
        a = TrendAnalyzer()
        assert a.most_common_rules() == []

    def test_most_common_rules(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", rules=("A", "B", "A")))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", rules=("A", "C")))
        common = a.most_common_rules(2)
        assert common[0] == ("A", 3)

    def test_overall_direction_improving(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", total=20))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", total=5))
        assert a.overall_direction() == "IMPROVING"

    def test_overall_direction_degrading(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", total=5))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", total=20))
        assert a.overall_direction() == "DEGRADING"

    def test_overall_direction_stable(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", total=10))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", total=10))
        assert a.overall_direction() == "STABLE"

    def test_overall_direction_unknown(self):
        a = TrendAnalyzer()
        assert a.overall_direction() == "UNKNOWN"


# ===========================================================================
# TrendAnalyzer - regressions
# ===========================================================================

class TestTrendAnalyzerRegressions:
    def test_no_regressions_empty(self):
        a = TrendAnalyzer()
        assert a.detect_regressions() == []

    def test_no_regressions_improving(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", total=10, score=20, rules=("A", "B")))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", total=5, score=10, rules=("A",)))
        regs = a.detect_regressions()
        assert len(regs) == 0

    def test_regression_new_rules(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", rules=("A",), score=5, high=1))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", rules=("A", "B", "C"), score=15, high=3))
        regs = a.detect_regressions()
        assert len(regs) == 1
        assert "B" in regs[0].new_rules
        assert "C" in regs[0].new_rules

    def test_regression_severity_increase(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", critical=0, score=5))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", critical=3, score=35))
        regs = a.detect_regressions()
        assert len(regs) == 1
        assert regs[0].severity_increase.get("CRITICAL") == 3
        assert regs[0].score_delta == 30


# ===========================================================================
# TrendAnalyzer - persistence
# ===========================================================================

class TestTrendAnalyzerPersistence:
    def test_to_json(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap())
        j = a.to_json()
        data = json.loads(j)
        assert data["version"] == "1.0"
        assert len(data["snapshots"]) == 1

    def test_from_json(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap())
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z"))
        j = a.to_json()
        a2 = TrendAnalyzer.from_json(j)
        assert a2.count == 2

    def test_roundtrip(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", critical=3))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", critical=1))
        j = a.to_json()
        a2 = TrendAnalyzer.from_json(j)
        assert a2.count == a.count
        assert a2.latest().critical == 1


# ===========================================================================
# TrendAnalyzer - report
# ===========================================================================

class TestTrendAnalyzerReport:
    def test_markdown_empty(self):
        a = TrendAnalyzer()
        report = a.markdown_report()
        assert "No scan data" in report

    def test_markdown_with_data(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", total=10, score=20, grade="D"))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", total=5, score=10, grade="B"))
        report = a.markdown_report()
        assert "# Security Trend Report" in report
        assert "Scans analyzed**: 2" in report
        assert "IMPROVING" in report
        assert "Scan History" in report

    def test_markdown_regressions_shown(self):
        a = TrendAnalyzer()
        a.add_snapshot(_snap(ts="2026-01-01T00:00:00Z", rules=("A",), score=5))
        a.add_snapshot(_snap(ts="2026-01-02T00:00:00Z", rules=("A", "B"), score=15))
        report = a.markdown_report()
        assert "Regressions" in report

    def test_ascii_chart_empty(self):
        a = TrendAnalyzer()
        assert a.ascii_chart([]) == "(no data)"

    def test_ascii_chart_with_data(self):
        a = TrendAnalyzer()
        points = [
            TrendPoint("2026-01-01", 10.0),
            TrendPoint("2026-01-02", 5.0),
            TrendPoint("2026-01-03", 15.0),
        ]
        chart = a.ascii_chart(points, height=5)
        assert "#" in chart
        assert "+" in chart

    def test_ascii_chart_zero_values(self):
        a = TrendAnalyzer()
        points = [TrendPoint("2026-01-01", 0.0)]
        chart = a.ascii_chart(points, height=3)
        assert "+" in chart
