"""
Tests for autopil.guard.ContextGuard

Coverage:
  - Allowed retrieval succeeds and returns context
  - Denied retrieval raises PermissionError
  - Audit event is recorded for every call (allow and deny)
  - Context hash is populated on ALLOW and None on DENY
  - Cross-agent isolation: second agent on same session is denied
  - Session isolation: different sessions are independent
  - Decorator preserves return value from wrapped function
"""

import uuid

import pytest

from autopil.guard import ContextGuard
from autopil.models import Decision, SensitivityLevel


# ── helpers ───────────────────────────────────────────────────────────────────

def make_retriever(return_value="context_data"):
    """A fake retrieval function that returns a fixed value."""
    def retrieve(query: str):
        return return_value
    return retrieve


# ── basic allow / deny ────────────────────────────────────────────────────────

class TestAllowDeny:
    def test_allowed_call_returns_context(self, guard):
        sid = str(uuid.uuid4())

        @guard.protect(agent_role="analyst", user_id="u1", source_id="reports",
                       sensitivity_level=SensitivityLevel.LOW, session_id=sid)
        def retrieve(query: str):
            return ["doc1", "doc2"]

        result = retrieve("find reports")
        assert result == ["doc1", "doc2"]

    def test_denied_call_raises_permission_error(self, guard):
        sid = str(uuid.uuid4())

        @guard.protect(agent_role="analyst", user_id="u1", source_id="executive_comms",
                       session_id=sid)
        def retrieve(query: str):
            return ["secret"]

        with pytest.raises(PermissionError, match="DENIED"):
            retrieve("find comms")

    def test_unknown_role_raises_permission_error(self, guard):
        @guard.protect(agent_role="rogue", user_id="u1", source_id="reports")
        def retrieve(query: str):
            return []

        with pytest.raises(PermissionError, match="DENIED"):
            retrieve("anything")

    def test_sensitivity_above_ceiling_is_denied(self, guard):
        """viewer max_sensitivity=medium — RESTRICTED should be denied."""
        sid = str(uuid.uuid4())

        @guard.protect(agent_role="viewer", user_id="u1", source_id="reports",
                       sensitivity_level=SensitivityLevel.RESTRICTED, session_id=sid)
        def retrieve(query: str):
            return []

        with pytest.raises(PermissionError):
            retrieve("sensitive query")


# ── audit trail ───────────────────────────────────────────────────────────────

class TestAuditTrail:
    def test_allow_creates_audit_event(self, guard):
        sid = str(uuid.uuid4())

        @guard.protect(agent_role="analyst", user_id="u1", source_id="reports",
                       session_id=sid)
        def retrieve(query: str):
            return "data"

        retrieve("q")
        events = guard.audit_log.get_session_events(sid)
        assert len(events) == 1
        assert events[0].decision == Decision.ALLOW

    def test_deny_creates_audit_event(self, guard):
        sid = str(uuid.uuid4())

        @guard.protect(agent_role="analyst", user_id="u1", source_id="executive_comms",
                       session_id=sid)
        def retrieve(query: str):
            return []

        with pytest.raises(PermissionError):
            retrieve("q")

        events = guard.audit_log.get_session_events(sid)
        assert len(events) == 1
        assert events[0].decision == Decision.DENY

    def test_audit_event_fields_match_request(self, guard):
        sid = str(uuid.uuid4())

        @guard.protect(agent_role="analyst", user_id="u42", source_id="reports",
                       session_id=sid)
        def retrieve(query: str):
            return "ok"

        retrieve("my query")
        event = guard.audit_log.get_session_events(sid)[0]
        assert event.agent_role  == "analyst"
        assert event.user_id     == "u42"
        assert event.source_id   == "reports"
        assert event.query       == "my query"
        assert event.session_id  == sid

    def test_get_audit_trail_convenience_method(self, guard):
        sid = str(uuid.uuid4())

        @guard.protect(agent_role="analyst", user_id="u1", source_id="reports",
                       session_id=sid)
        def retrieve(query: str):
            return "data"

        retrieve("q")
        trail = guard.get_audit_trail(sid)
        assert len(trail) == 1


# ── context hash ──────────────────────────────────────────────────────────────

class TestContextHash:
    def test_context_hash_set_on_allow(self, guard):
        sid = str(uuid.uuid4())

        @guard.protect(agent_role="analyst", user_id="u1", source_id="reports",
                       session_id=sid)
        def retrieve(query: str):
            return "some context"

        retrieve("q")
        event = guard.audit_log.get_session_events(sid)[0]
        assert event.context_hash is not None
        assert len(event.context_hash) == 16  # first 16 chars of sha256 hex

    def test_context_hash_none_on_deny(self, guard):
        sid = str(uuid.uuid4())

        @guard.protect(agent_role="analyst", user_id="u1", source_id="executive_comms",
                       session_id=sid)
        def retrieve(query: str):
            return "secret"

        with pytest.raises(PermissionError):
            retrieve("q")

        event = guard.audit_log.get_session_events(sid)[0]
        assert event.context_hash is None


# ── cross-agent isolation ─────────────────────────────────────────────────────

class TestCrossAgentIsolation:
    def test_second_agent_on_same_session_is_denied(self, guard):
        """Session established by analyst — viewer trying same session must be denied."""
        sid = str(uuid.uuid4())

        @guard.protect(agent_role="analyst", user_id="u1", source_id="reports",
                       session_id=sid)
        def analyst_retrieve(query: str):
            return "analyst data"

        @guard.protect(agent_role="viewer", user_id="u2", source_id="reports",
                       session_id=sid)
        def viewer_retrieve(query: str):
            return "viewer data"

        analyst_retrieve("q")  # establishes session ownership

        with pytest.raises(PermissionError, match="cross_agent_isolation|owned by"):
            viewer_retrieve("q")

    def test_cross_agent_deny_is_audited(self, guard):
        sid = str(uuid.uuid4())

        @guard.protect(agent_role="analyst", user_id="u1", source_id="reports",
                       session_id=sid)
        def analyst_retrieve(query: str):
            return "data"

        @guard.protect(agent_role="viewer", user_id="u2", source_id="reports",
                       session_id=sid)
        def viewer_retrieve(query: str):
            return "data"

        analyst_retrieve("q")

        with pytest.raises(PermissionError):
            viewer_retrieve("q")

        events = guard.audit_log.get_session_events(sid)
        assert len(events) == 2
        isolation_event = events[1]
        assert isolation_event.decision    == Decision.DENY
        assert isolation_event.policy_name == "cross_agent_isolation"

    def test_same_agent_can_reuse_session(self, guard):
        """Same agent role on the same session should continue to work."""
        sid = str(uuid.uuid4())

        @guard.protect(agent_role="analyst", user_id="u1", source_id="reports",
                       session_id=sid)
        def retrieve(query: str):
            return f"data for {query}"

        result1 = retrieve("first")
        result2 = retrieve("second")
        assert result1 is not None
        assert result2 is not None

        events = guard.audit_log.get_session_events(sid)
        assert len(events) == 2
        assert all(e.decision == Decision.ALLOW for e in events)

    def test_different_sessions_are_independent(self, guard):
        """Two agents on different sessions should each succeed independently."""
        sid_a = str(uuid.uuid4())
        sid_b = str(uuid.uuid4())

        @guard.protect(agent_role="analyst", user_id="u1", source_id="reports",
                       session_id=sid_a)
        def analyst_retrieve(query: str):
            return "analyst"

        @guard.protect(agent_role="viewer", user_id="u2", source_id="reports",
                       session_id=sid_b)
        def viewer_retrieve(query: str):
            return "viewer"

        r1 = analyst_retrieve("q")
        r2 = viewer_retrieve("q")
        assert r1 == "analyst"
        assert r2 == "viewer"
