import re
from types import SimpleNamespace

from prowler.lib.outputs.compliance.cis.cis import get_cis_table


def _strip_ansi(text):
    return re.sub(r"\x1b\[[0-9;]*m", "", text)


def _make_finding(check_id, status="PASS", muted=False):
    return SimpleNamespace(
        check_metadata=SimpleNamespace(CheckID=check_id),
        status=status,
        muted=muted,
    )


def _attr(section, profile="Level 1"):
    return SimpleNamespace(Section=section, Profile=profile)


def _make_compliance(provider, attributes, version="1.4", framework="CIS"):
    """Build a per-check CIS compliance with the given (section, profile) attrs."""
    return SimpleNamespace(
        Framework=framework,
        Version=version,
        Provider=provider,
        Requirements=[SimpleNamespace(Attributes=attributes)],
    )


def _make_compliance_multi_req(provider, attributes, version="1.4", framework="CIS"):
    """Build a per-check CIS compliance where each attr is its own requirement,
    simulating a check that appears in several requirements."""
    return SimpleNamespace(
        Framework=framework,
        Version=version,
        Provider=provider,
        Requirements=[SimpleNamespace(Attributes=[attr]) for attr in attributes],
    )


class TestCISTable:
    """Verify multi-section counting and provider-column attribution for the CIS compliance table."""

    def test_muted_multi_section_not_undercounted(self, capsys, tmp_path):
        """A single MUTED finding mapped to several sections must increment the
        per-section Muted column for every section, not only the first seen.

        CIS counts FAIL/PASS through Level 1/Level 2 buckets, so only the Muted
        per-section count was affected by the undercount bug.
        """
        bulk_metadata = {
            # check_a is muted and belongs to two sections at once.
            "check_a": SimpleNamespace(
                Compliance=[
                    _make_compliance("aws", [_attr("1 IAM"), _attr("2 Logging")])
                ]
            ),
            # A real (non-muted) finding so the table is rendered.
            "check_b": SimpleNamespace(
                Compliance=[_make_compliance("aws", [_attr("1 IAM")])]
            ),
        }
        findings = [
            _make_finding("check_a", "FAIL", muted=True),
            _make_finding("check_b", "PASS"),
        ]

        get_cis_table(
            findings,
            bulk_metadata,
            "cis_1.4_aws",
            "output",
            str(tmp_path),
            False,
        )

        captured = capsys.readouterr()
        plain = _strip_ansi(captured.out)
        # Both section rows must carry a Muted count of 1 in their last cell.
        # Before the fix only the first section seen got incremented.
        muted_one_rows = re.findall(r"│\s*1\s*│\s*$", plain, flags=re.MULTILINE)
        assert len(muted_one_rows) == 2

    def test_same_section_level_not_double_counted(self, capsys, tmp_path):
        """A single finding whose check maps to several requirements that share
        the same section and profile must count once for that section/level,
        not once per requirement (FAIL(1), never FAIL(2))."""
        bulk_metadata = {
            # check_a is a single FAIL mapped to two requirements, both in the
            # same section "1 IAM" and the same profile "Level 1".
            "check_a": SimpleNamespace(
                Compliance=[
                    _make_compliance_multi_req("aws", [_attr("1 IAM"), _attr("1 IAM")])
                ]
            ),
            # A second finding in another section so the table renders.
            "check_b": SimpleNamespace(
                Compliance=[_make_compliance("aws", [_attr("2 Logging")])]
            ),
        }
        findings = [
            _make_finding("check_a", "FAIL"),
            _make_finding("check_b", "PASS"),
        ]

        get_cis_table(
            findings,
            bulk_metadata,
            "cis_1.4_aws",
            "output",
            str(tmp_path),
            False,
        )

        captured = capsys.readouterr()
        plain = _strip_ansi(captured.out)
        # The "1 IAM" row must show FAIL(1) for Level 1, never FAIL(2).
        assert "FAIL(1)" in plain
        assert "FAIL(2)" not in plain

    def test_provider_column_not_leaked_from_other_framework(self, capsys, tmp_path):
        """The Provider column must come from the matched CIS compliance, not
        from a different framework that trails it in the compliance list."""
        bulk_metadata = {
            "check_a": SimpleNamespace(
                Compliance=[
                    _make_compliance("aws", [_attr("1 IAM")]),
                    _make_compliance(
                        "gcp", [_attr("Other")], framework="OtherFramework"
                    ),
                ]
            ),
            "check_b": SimpleNamespace(
                Compliance=[
                    _make_compliance("aws", [_attr("1 IAM")]),
                    _make_compliance(
                        "gcp", [_attr("Other")], framework="OtherFramework"
                    ),
                ]
            ),
        }
        findings = [
            _make_finding("check_a", "FAIL"),
            _make_finding("check_b", "PASS"),
        ]

        get_cis_table(
            findings,
            bulk_metadata,
            "cis_1.4_aws",
            "output",
            str(tmp_path),
            False,
        )

        captured = capsys.readouterr()
        assert "aws" in captured.out
        # The trailing unrelated framework's provider must not leak in.
        assert "gcp" not in captured.out
