"""Tests for IAMFinding, classify_risk_level, conversions, patches."""

from __future__ import annotations

import pytest

from complyform.core.assessor.control_matcher import Finding
from complyform.core.cloud_intelligence.iam_analyzer import (
    IAM_FRAMEWORK_CONTROL_MAP,
    IAMAnalysisResult,
    IAMFinding,
    classify_risk_level,
    generate_iam_patches,
    iam_finding_to_findings,
)
from complyform.core.remediator.patch_generator import PatchFile


def _make_iam_finding(**overrides: object) -> IAMFinding:
    """Create an IAMFinding with sensible defaults."""
    defaults: dict[str, object] = {
        "cloud": "gcp",
        "principal": "sa@project.iam.gserviceaccount.com",
        "principal_type": "service_account",
        "current_role": "roles/editor",
        "recommended_role": "roles/viewer",
        "unused_permissions": ["storage.buckets.delete", "compute.instances.delete"],
        "last_activity": "2025-01-01T00:00:00Z",
        "risk_level": "HIGH",
        "framework_controls": [{"framework": "soc2", "control_id": "CC6.1"}],
        "remediation_terraform": None,
        "source_api": "iam_recommender",
    }
    defaults.update(overrides)
    return IAMFinding(**defaults)  # type: ignore[arg-type]


class TestIAMFinding:
    """Tests for IAMFinding dataclass."""

    def test_construction_all_fields(self) -> None:
        finding = _make_iam_finding()
        assert finding.cloud == "gcp"
        assert finding.principal == "sa@project.iam.gserviceaccount.com"
        assert finding.principal_type == "service_account"
        assert finding.current_role == "roles/editor"
        assert finding.recommended_role == "roles/viewer"
        assert len(finding.unused_permissions) == 2
        assert finding.last_activity == "2025-01-01T00:00:00Z"
        assert finding.risk_level == "HIGH"
        assert len(finding.framework_controls) == 1
        assert finding.remediation_terraform is None
        assert finding.source_api == "iam_recommender"


class TestClassifyRiskLevel:
    """Tests for classify_risk_level."""

    @pytest.mark.parametrize(
        ("role", "last_activity", "unused_perms", "expected"),
        [
            ("roles/owner", "2025-01-01T00:00:00Z", [], "CRITICAL"),
            ("AdministratorAccess", "2025-01-01T00:00:00Z", [], "CRITICAL"),
            ("Owner", "2025-01-01T00:00:00Z", [], "CRITICAL"),
            ("roles/editor", "2025-01-01T00:00:00Z", [], "HIGH"),
            ("Contributor", "2025-01-01T00:00:00Z", [], "HIGH"),
            ("roles/viewer", None, [], "HIGH"),  # No activity
            (
                "roles/viewer",
                "2025-01-01T00:00:00Z",
                ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"],
                "MEDIUM",
            ),
            ("roles/viewer", "2025-01-01T00:00:00Z", ["a", "b", "c"], "LOW"),
        ],
    )
    def test_classification(
        self,
        role: str,
        last_activity: str | None,
        unused_perms: list[str],
        expected: str,
    ) -> None:
        assert classify_risk_level(role, last_activity, unused_perms) == expected


class TestIAMFindingToFindings:
    """Tests for iam_finding_to_findings conversion."""

    def test_soc2_produces_three_findings(self) -> None:
        iam = _make_iam_finding()
        findings = iam_finding_to_findings(iam, ["soc2"])
        assert len(findings) == 3
        control_ids = {f.control_id for f in findings}
        assert control_ids == {"CC6.1", "CC6.2", "CC6.3"}

    def test_soc2_and_hipaa_produces_four_findings(self) -> None:
        iam = _make_iam_finding()
        findings = iam_finding_to_findings(iam, ["soc2", "hipaa"])
        assert len(findings) == 4

    def test_finding_fields(self) -> None:
        iam = _make_iam_finding()
        findings = iam_finding_to_findings(iam, ["soc2"])
        for f in findings:
            assert isinstance(f, Finding)
            assert f.resource_type == "iam_binding"
            assert f.checkov_id is None
            assert f.confidence == "verified"
            assert f.resource_address == iam.principal
            assert f.status == "FAIL"
            assert f.severity == iam.risk_level

    def test_unknown_framework_produces_no_findings(self) -> None:
        iam = _make_iam_finding()
        findings = iam_finding_to_findings(iam, ["unknown_framework"])
        assert len(findings) == 0


class TestGenerateIAMPatches:
    """Tests for generate_iam_patches."""

    def test_patch_with_recommended_role(self) -> None:
        iam = _make_iam_finding(recommended_role="roles/viewer")
        patches = generate_iam_patches([iam], "gcp")
        assert len(patches) == 1
        patch = patches[0]
        assert isinstance(patch, PatchFile)
        assert patch.filename.startswith("iam_")
        assert "google_project_iam_member" in patch.content
        assert "roles/viewer" in patch.content

    def test_patch_with_remove(self) -> None:
        iam = _make_iam_finding(recommended_role=None)
        patches = generate_iam_patches([iam], "gcp")
        assert len(patches) == 1
        patch = patches[0]
        assert "REMOVE" in patch.content

    def test_header_comment_fields(self) -> None:
        iam = _make_iam_finding()
        patches = generate_iam_patches([iam], "gcp")
        patch = patches[0]
        assert iam.principal in patch.content
        assert iam.current_role in patch.content
        assert iam.risk_level in patch.content

    def test_aws_patch(self) -> None:
        iam = _make_iam_finding(
            cloud="aws",
            principal="arn:aws:iam::123:role/my-role",
            current_role="AdministratorAccess",
            recommended_role="arn:aws:iam::aws:policy/ReadOnlyAccess",
        )
        patches = generate_iam_patches([iam], "aws")
        assert len(patches) == 1
        assert "aws_iam_role_policy_attachment" in patches[0].content

    def test_azure_patch(self) -> None:
        iam = _make_iam_finding(
            cloud="azure",
            principal="abc-def-123",
            current_role="Owner",
            recommended_role="Reader",
        )
        patches = generate_iam_patches([iam], "azure")
        assert len(patches) == 1
        assert "azurerm_role_assignment" in patches[0].content


class TestIAMFrameworkControlMap:
    """Tests for IAM_FRAMEWORK_CONTROL_MAP constant."""

    def test_has_all_frameworks(self) -> None:
        expected = {"soc2", "hipaa", "iso27001", "nist_800_53", "pci_dss"}
        assert set(IAM_FRAMEWORK_CONTROL_MAP.keys()) == expected

    def test_soc2_has_three_controls(self) -> None:
        assert len(IAM_FRAMEWORK_CONTROL_MAP["soc2"]) == 3

    def test_entries_have_framework_and_control_id(self) -> None:
        for fw, controls in IAM_FRAMEWORK_CONTROL_MAP.items():
            for ctrl in controls:
                assert "framework" in ctrl
                assert "control_id" in ctrl
                assert ctrl["framework"] == fw


class TestIAMAnalysisResult:
    """Tests for IAMAnalysisResult dataclass."""

    def test_extends_cloud_intelligence_result(self) -> None:
        result = IAMAnalysisResult(
            feature="iam_analysis",
            cloud="gcp",
            success=True,
            error=None,
            duration_ms=100,
            timestamp="2025-01-01T00:00:00Z",
            findings=[],
            principals_scanned=10,
            over_provisioned_count=5,
            unused_role_count=2,
        )
        assert result.principals_scanned == 10
        assert result.over_provisioned_count == 5
        assert result.unused_role_count == 2
        assert result.findings == []
