"""Tests for SOC2 control mapping."""

from __future__ import annotations

from attestmcp.models import (
    AgentIdentity,
    AuditRecord,
    ChainVerification,
    IdentitySource,
    PolicyDecision,
    TrustLevel,
)
from attestmcp.soc2 import map_controls


def _make_record(
    *,
    method: str = "tools/call",
    tool_name: str | None = "store_memory",
    user: str | None = None,
    agent_name: str | None = None,
    org: str | None = None,
    role: str | None = None,
    source: IdentitySource = IdentitySource.ANONYMOUS,
    trust_level: TrustLevel = TrustLevel.ANONYMOUS,
    policy: PolicyDecision = PolicyDecision.ALLOW,
    error: str | None = None,
    session_id: str | None = None,
    duration_ms: int = 5,
) -> AuditRecord:
    return AuditRecord(
        session_id=session_id,
        agent_identity=AgentIdentity(
            source=source,
            user=user,
            agent_name=agent_name,
            org=org,
            role=role,
            session_id=session_id,
            trust_level=trust_level,
            verified=trust_level == TrustLevel.VERIFIED,
        ),
        method=method,
        tool_name=tool_name,
        params_hash="abc123",
        policy_decision=policy,
        result_hash="def456",
        duration_ms=duration_ms,
        error=error,
    )


def _valid_chain(n: int = 5) -> ChainVerification:
    return ChainVerification(
        valid=True,
        records_checked=n,
        first_record_id="first",
        last_record_id="last",
    )


def _broken_chain() -> ChainVerification:
    return ChainVerification(
        valid=False,
        records_checked=3,
        first_record_id="first",
        last_record_id="last",
        break_at_record_id="record-3",
        expected_hash="aaa",
        actual_hash="bbb",
    )


class TestMapControls:
    """Integration tests for the full SOC2 mapping pipeline."""

    def test_returns_four_controls(self) -> None:
        records = [_make_record()]
        report = map_controls(records, _valid_chain(1))
        assert len(report.controls) == 4

    def test_control_ids(self) -> None:
        report = map_controls([_make_record()], _valid_chain(1))
        ids = [c.control_id for c in report.controls]
        assert ids == ["CC6.1", "CC6.3", "CC7.2", "CC8.1"]

    def test_empty_records(self) -> None:
        report = map_controls([], _valid_chain(0))
        assert report.total_records == 0
        # CC7.2 should not be satisfied with zero records
        cc72 = next(c for c in report.controls if c.control_id == "CC7.2")
        assert cc72.satisfied is False


class TestCC61:
    """CC6.1 — Logical Access Controls."""

    def test_all_anonymous_flags_gap(self) -> None:
        records = [_make_record() for _ in range(5)]
        report = map_controls(records, _valid_chain(5))
        cc61 = next(c for c in report.controls if c.control_id == "CC6.1")
        assert cc61.satisfied is False
        assert any("anonymous" in g.lower() or "authentication" in g.lower() for g in cc61.gaps)

    def test_all_identified_no_gap(self) -> None:
        records = [
            _make_record(
                user="alice@acme.com",
                source=IdentitySource.OAUTH_JWT,
                trust_level=TrustLevel.VERIFIED,
            )
            for _ in range(5)
        ]
        report = map_controls(records, _valid_chain(5))
        cc61 = next(c for c in report.controls if c.control_id == "CC6.1")
        assert cc61.satisfied is True
        assert len(cc61.gaps) == 0

    def test_counts_verified_identity(self) -> None:
        records = [
            _make_record(
                user="alice@acme.com",
                source=IdentitySource.OAUTH_JWT,
                trust_level=TrustLevel.VERIFIED,
            )
        ]
        report = map_controls(records, _valid_chain(1))
        cc61 = next(c for c in report.controls if c.control_id == "CC6.1")
        assert any("cryptographically verified" in e for e in cc61.evidence_points)


class TestCC63:
    """CC6.3 — Access Authorization."""

    def test_captures_policy_decisions(self) -> None:
        records = [
            _make_record(policy=PolicyDecision.ALLOW),
            _make_record(policy=PolicyDecision.DENY),
        ]
        report = map_controls(records, _valid_chain(2))
        cc63 = next(c for c in report.controls if c.control_id == "CC6.3")
        assert any("ALLOW" in e for e in cc63.evidence_points)
        assert any("DENY" in e for e in cc63.evidence_points)

    def test_detects_missing_roles(self) -> None:
        records = [_make_record()]
        report = map_controls(records, _valid_chain(1))
        cc63 = next(c for c in report.controls if c.control_id == "CC6.3")
        assert any("role" in g.lower() for g in cc63.gaps)

    def test_shows_roles_when_present(self) -> None:
        records = [
            _make_record(role="admin", source=IdentitySource.OAUTH_JWT),
            _make_record(role="viewer", source=IdentitySource.OAUTH_JWT),
        ]
        report = map_controls(records, _valid_chain(2))
        cc63 = next(c for c in report.controls if c.control_id == "CC6.3")
        assert any("admin" in e for e in cc63.evidence_points)
        assert len(cc63.gaps) == 0


class TestCC72:
    """CC7.2 — System Monitoring."""

    def test_captures_tool_invocations(self) -> None:
        records = [_make_record(method="tools/call", tool_name="store_memory")]
        report = map_controls(records, _valid_chain(1))
        cc72 = next(c for c in report.controls if c.control_id == "CC7.2")
        assert any("tool invocations" in e.lower() for e in cc72.evidence_points)

    def test_reports_errors(self) -> None:
        records = [_make_record(error="upstream timeout")]
        report = map_controls(records, _valid_chain(1))
        cc72 = next(c for c in report.controls if c.control_id == "CC7.2")
        assert any("error" in e.lower() for e in cc72.evidence_points)

    def test_tracks_sessions(self) -> None:
        records = [
            _make_record(session_id="sess-1"),
            _make_record(session_id="sess-2"),
            _make_record(session_id="sess-1"),
        ]
        report = map_controls(records, _valid_chain(3))
        cc72 = next(c for c in report.controls if c.control_id == "CC7.2")
        assert any("2 unique MCP sessions" in e for e in cc72.evidence_points)


class TestCC81:
    """CC8.1 — Change Management (tamper-evident chain)."""

    def test_valid_chain_satisfied(self) -> None:
        records = [_make_record()]
        report = map_controls(records, _valid_chain(1))
        cc81 = next(c for c in report.controls if c.control_id == "CC8.1")
        assert cc81.satisfied is True
        assert any("integrity verified" in e.lower() for e in cc81.evidence_points)

    def test_broken_chain_not_satisfied(self) -> None:
        records = [_make_record()]
        report = map_controls(records, _broken_chain())
        cc81 = next(c for c in report.controls if c.control_id == "CC8.1")
        assert cc81.satisfied is False
        assert any("FAILURE" in g for g in cc81.gaps)

    def test_mentions_append_only(self) -> None:
        records = [_make_record()]
        report = map_controls(records, _valid_chain(1))
        cc81 = next(c for c in report.controls if c.control_id == "CC8.1")
        assert any("append-only" in e.lower() for e in cc81.evidence_points)
