"""Tests for complyform.core.assessor.scoring – severity-weighted compliance scoring."""

from __future__ import annotations

from complyform.core.assessor.control_matcher import Finding
from complyform.core.assessor.scoring import SEVERITY_WEIGHTS, compute_compliance_score


def _finding(
    *,
    status: str = "PASS",
    severity: str = "MEDIUM",
    control_id: str = "C-1",
    framework: str = "test-fw",
    resource_address: str = "module.x.res",
    resource_type: str = "aws_s3_bucket",
    finding: str = "test finding",
    remediation: str = "",
    checkov_id: str | None = None,
    cross_mappings: list | None = None,
    native_ids: dict | None = None,
    confidence: str = "high",
    not_assessed_reason: str | None = None,
) -> Finding:
    """Build a Finding with sensible defaults; override only what matters."""
    return Finding(
        control_id=control_id,
        framework=framework,
        resource_address=resource_address,
        resource_type=resource_type,
        status=status,
        severity=severity,
        finding=finding,
        remediation=remediation,
        checkov_id=checkov_id,
        cross_mappings=cross_mappings,
        native_ids=native_ids,
        confidence=confidence,
        not_assessed_reason=not_assessed_reason,
    )


class TestComputeComplianceScore:
    """Tests for compute_compliance_score."""

    def test_all_pass_returns_100(self) -> None:
        findings = [
            _finding(status="PASS", severity="CRITICAL"),
            _finding(status="PASS", severity="HIGH"),
            _finding(status="PASS", severity="MEDIUM"),
            _finding(status="PASS", severity="LOW"),
        ]
        assert compute_compliance_score(findings) == 100.0

    def test_all_fail_returns_0(self) -> None:
        findings = [
            _finding(status="FAIL", severity="CRITICAL"),
            _finding(status="FAIL", severity="HIGH"),
            _finding(status="FAIL", severity="MEDIUM"),
            _finding(status="FAIL", severity="LOW"),
        ]
        assert compute_compliance_score(findings) == 0.0

    def test_mixed_pass_fail_weighted_correctly(self) -> None:
        """One CRITICAL PASS (w=10) + one HIGH FAIL (w=5) → 10/15 * 100 = 66.7."""
        findings = [
            _finding(status="PASS", severity="CRITICAL"),
            _finding(status="FAIL", severity="HIGH"),
        ]
        score = compute_compliance_score(findings)
        # (10) / (10 + 5) * 100 = 66.666... → rounded to 66.7
        assert score == 66.7

    def test_only_manual_review_returns_100(self) -> None:
        findings = [
            _finding(status="MANUAL_REVIEW", severity="HIGH"),
            _finding(status="MANUAL_REVIEW", severity="CRITICAL"),
        ]
        assert compute_compliance_score(findings) == 100.0

    def test_only_not_assessed_returns_100(self) -> None:
        findings = [
            _finding(
                status="NOT_ASSESSED", severity="LOW", not_assessed_reason="no check"
            ),
            _finding(
                status="NOT_ASSESSED", severity="MEDIUM", not_assessed_reason="n/a"
            ),
        ]
        assert compute_compliance_score(findings) == 100.0

    def test_empty_findings_returns_100(self) -> None:
        assert compute_compliance_score([]) == 100.0

    def test_rounding_to_one_decimal(self) -> None:
        """3 LOW PASS (w=3) + 1 MEDIUM FAIL (w=2) → 3/5 * 100 = 60.0 exact."""
        findings = [
            _finding(status="PASS", severity="LOW"),
            _finding(status="PASS", severity="LOW"),
            _finding(status="PASS", severity="LOW"),
            _finding(status="FAIL", severity="MEDIUM"),
        ]
        score = compute_compliance_score(findings)
        assert score == 60.0
        # Also verify it truly is a float with at most 1 decimal
        assert score == round(score, 1)

    def test_rounding_non_trivial(self) -> None:
        """1 MEDIUM PASS (w=2) + 1 LOW FAIL (w=1) → 2/3 * 100 = 66.666... → 66.7."""
        findings = [
            _finding(status="PASS", severity="MEDIUM"),
            _finding(status="FAIL", severity="LOW"),
        ]
        assert compute_compliance_score(findings) == 66.7

    def test_single_critical_fail_multiple_low_pass(self) -> None:
        """1 CRITICAL FAIL (w=10) + 5 LOW PASS (w=5) → 5/15 * 100 = 33.3."""
        findings = [
            _finding(status="FAIL", severity="CRITICAL"),
            *[_finding(status="PASS", severity="LOW") for _ in range(5)],
        ]
        score = compute_compliance_score(findings)
        # passed=5*1=5, total=10+5=15, score=5/15*100≈33.3
        assert score == 33.3

    def test_excluded_statuses_do_not_affect_score(self) -> None:
        """MANUAL_REVIEW and NOT_ASSESSED mixed with scoreable findings are ignored."""
        findings = [
            _finding(status="PASS", severity="HIGH"),
            _finding(status="FAIL", severity="HIGH"),
            _finding(status="MANUAL_REVIEW", severity="CRITICAL"),
            _finding(status="NOT_ASSESSED", severity="CRITICAL"),
        ]
        # Only 2 scoreable: 1 PASS HIGH (5) + 1 FAIL HIGH (5) → 5/10 = 50.0
        assert compute_compliance_score(findings) == 50.0

    def test_unknown_severity_defaults_to_weight_1(self) -> None:
        """Severity not in SEVERITY_WEIGHTS gets weight 1."""
        findings = [
            _finding(status="FAIL", severity="UNKNOWN"),
        ]
        # total=1, failed=1 → 0.0
        assert compute_compliance_score(findings) == 0.0

    def test_severity_weights_constant(self) -> None:
        assert SEVERITY_WEIGHTS == {
            "CRITICAL": 10,
            "HIGH": 5,
            "MEDIUM": 2,
            "LOW": 1,
        }
