"""Integration tests for the egress gate on the real execution path.

These cover ``AuthorizedExecutionService.execute()`` consulting
``EgressGate.authorize()`` before the provider runs
(``src/cyberbrain26/execution_plane/secure_gateway.py``). That path had
no integration test: ``tests/test_egress_gate.py`` covers the gate in
isolation, but not its position in the execution chain.
"""

from __future__ import annotations

import hashlib
import threading
import time
from pathlib import Path
from typing import Any

import pytest

from cyberbrain26.data_plane import SQLiteDatabase
from cyberbrain26.execution_plane.bubblewrap_provider import BubblewrapExecutionProvider
from cyberbrain26.execution_plane.secure_gateway import (
    AuthorizedExecutionService,
    ExecutionRefused,
)
from cyberbrain26.security_plane import (
    AuditEventType,
    AuditLog,
    AuditQuery,
    EgressGate,
    EgressPolicy,
    SecurityError,
)
from cyberbrain26.security_plane.approval import ApprovalManager
from cyberbrain26.security_plane.identity import AgentIdentity, TrustLevel
from cyberbrain26.security_plane.permission import PermissionGateway
from cyberbrain26.security_plane.policy import (
    PolicyDecision,
    PolicyEngine,
    PolicyMode,
    PolicyRule,
)


class _OrderingStubProvider:
    """Local provider stub that records when it was called.

    It also counts the ``model_egress`` audit rows at the moment it
    runs, which proves the egress decision was recorded *before* the
    provider executed anything.
    """

    def __init__(self, audit: AuditLog) -> None:
        self.audit = audit
        self.calls: list[dict[str, Any]] = []
        self.egress_rows_at_call_time: int | None = None

    @property
    def provider_name(self) -> str:
        return "pilot-stub"

    async def execute(
        self,
        code: str,
        language: str,
        *,
        timeout_seconds: int = 30,
        workspace: str | None = None,
        allow_network: bool = False,
    ) -> dict[str, Any]:
        self.calls.append({"code": code, "language": language})
        self.egress_rows_at_call_time = len(
            [
                event
                for event in self.audit.query(AuditQuery(event_type=AuditEventType.DATA_ACCESS))
                if event.action == "model_egress"
            ]
        )
        return {"return_code": 0, "stdout": "", "stderr": "", "timed_out": False}


def _identity() -> AgentIdentity:
    return AgentIdentity(
        agent_id="execution-agent",
        role="coder",
        trust_level=TrustLevel.TRUSTED,
        scopes={"python"},
    )


def _egress_events(audit: AuditLog) -> list[Any]:
    return [
        event
        for event in audit.query(AuditQuery(event_type=AuditEventType.DATA_ACCESS))
        if event.action == "model_egress"
    ]


class _Approver(threading.Thread):
    """Approve the ``execute_python`` request the service raises in SAFE mode."""

    def __init__(self, approval_manager: ApprovalManager) -> None:
        super().__init__(daemon=True)
        self.approval_manager = approval_manager
        self._stop_event = threading.Event()

    def run(self) -> None:
        while not self._stop_event.is_set():
            for request in self.approval_manager.list_pending():
                if request.action == "execute_python":
                    self.approval_manager.approve(request.request_id, "test-owner")
            time.sleep(0.02)

    def stop(self) -> None:
        self._stop_event.set()
        self.join(timeout=5)


async def test_execute_records_local_egress_decision_before_running_code(tmp_path) -> None:
    """The egress decision is recorded before the provider runs, as a digest."""
    audit = AuditLog(database=SQLiteDatabase(tmp_path / "egress.sqlite3"))
    gateway = PermissionGateway(PolicyEngine(mode=PolicyMode.FULL))
    provider = _OrderingStubProvider(audit)
    service = AuthorizedExecutionService(provider, gateway, audit_log=audit)

    code = "print('unique-payload-marker-42')"
    gateway.policy_engine.add_rule(
        PolicyRule(
            name="allow_execute_python",
            action_pattern="execute_python",
            decision=PolicyDecision.ALLOW,
            description="Test: explicit allow for execute_python",
        )
    )
    result = await service.execute(_identity(), code, "python", task_id="egress-task")

    assert result["return_code"] == 0
    assert provider.calls, "provider must have run"
    # Ordering proof: the decision was already recorded when the provider started.
    assert provider.egress_rows_at_call_time == 1

    events = _egress_events(audit)
    assert len(events) == 1
    event = events[0]
    assert event.result == "allowed"
    assert event.network_action == "local"
    assert event.metadata["payload_sha256"] == hashlib.sha256(code.encode()).hexdigest()
    # The audit row carries the digest, never the payload itself.
    assert "unique-payload-marker-42" not in str(event.metadata)


async def test_no_egress_record_when_permission_gate_refuses(tmp_path) -> None:
    """A request refused by the permission gate never reaches the egress gate."""
    audit = AuditLog(database=SQLiteDatabase(tmp_path / "egress.sqlite3"))
    gateway = PermissionGateway(PolicyEngine(mode=PolicyMode.FULL))
    provider = _OrderingStubProvider(audit)
    service = AuthorizedExecutionService(provider, gateway, audit_log=audit)

    unscoped = AgentIdentity(
        agent_id="unscoped-agent", role="coder", trust_level=TrustLevel.TRUSTED, scopes=set()
    )
    with pytest.raises(SecurityError, match="lacks required scope"):
        await service.execute(unscoped, "print('never')", "python", task_id="egress-refused")

    assert provider.calls == []
    assert _egress_events(audit) == []


async def test_safe_mode_full_chain_identity_policy_approval_egress_provider(tmp_path) -> None:
    """SAFE mode: identity, policy, approval, egress, then the provider."""
    audit = AuditLog(database=SQLiteDatabase(tmp_path / "egress.sqlite3"))
    approval_manager = ApprovalManager(database=SQLiteDatabase(tmp_path / "approvals.sqlite3"))
    gateway = PermissionGateway(PolicyEngine(mode=PolicyMode.SAFE))
    provider = _OrderingStubProvider(audit)
    service = AuthorizedExecutionService(
        provider,
        gateway,
        approval_manager=approval_manager,
        audit_log=audit,
    )
    approver = _Approver(approval_manager)
    approver.start()
    try:
        result = await service.execute(
            _identity(), "print('safe-mode')", "python", task_id="egress-safe"
        )
    finally:
        approver.stop()

    assert result["return_code"] == 0
    assert provider.calls
    assert provider.egress_rows_at_call_time == 1
    assert len(_egress_events(audit)) == 1
    granted = [
        event
        for event in audit.query(AuditQuery(event_type=AuditEventType.APPROVAL_GRANTED))
        if event.action == "execute_python"
    ]
    assert granted, "expected the human approval to be recorded before execution"

async def test_egress_denial_refuses_execution_and_never_calls_provider(tmp_path) -> None:
    """A denied egress decision stops the execution before the provider runs.

    ``AuthorizedExecutionService.__init__`` builds its own
    ``EgressPolicy(local_provider_ids={provider_name})``
    (``secure_gateway.py``), so on this path the gate cannot be denied
    by configuration today. The gate is therefore replaced here with one
    whose allowlists are empty, to prove the gate sits on the real path
    and fails closed. That limitation is reported to the owner, not
    silently changed in this test.
    """
    audit = AuditLog(database=SQLiteDatabase(tmp_path / "egress.sqlite3"))
    gateway = PermissionGateway(PolicyEngine(mode=PolicyMode.FULL))
    provider = _OrderingStubProvider(audit)
    service = AuthorizedExecutionService(provider, gateway, audit_log=audit)
    service.egress_gate = EgressGate(EgressPolicy(), audit_log=audit, base_decision=True)
    gateway.policy_engine.add_rule(
        PolicyRule(
            name="allow_execute_python",
            action_pattern="execute_python",
            decision=PolicyDecision.ALLOW,
            description="Test: explicit allow for execute_python",
        )
    )

    with pytest.raises(ExecutionRefused, match="egress denied"):
        await service.execute(_identity(), "print('never')", "python", task_id="egress-denied")

    assert provider.calls == []
    events = _egress_events(audit)
    assert len(events) == 1
    assert events[0].result == "denied"


async def test_execute_records_bubblewrap_egress_and_sandbox_execution(tmp_path, sandbox_workspaces_root: Path) -> None:
    """Real E2E path: identity → policy → approval → egress gate → bubblewrap sandbox → audit."""
    audit = AuditLog(database=SQLiteDatabase(tmp_path / "egress.sqlite3"))
    gateway = PermissionGateway(PolicyEngine(mode=PolicyMode.FULL))
    provider = BubblewrapExecutionProvider(workspaces_root=sandbox_workspaces_root)
    service = AuthorizedExecutionService(
        provider, 
        gateway,
        audit_log=audit,
        approval_timeout_seconds=0.1,  # Short timeout for fast test
        approval_poll_interval=0.01,
    )

    code = "print('sandbox-egress-test-42')"
    gateway.policy_engine.add_rule(
        PolicyRule(
            name="allow_execute_python",
            action_pattern="execute_python",
            decision=PolicyDecision.ALLOW,
            description="Test: explicit allow for execute_python",
        )
    )
    result = await service.execute(
        _identity(), 
        code, 
        "python", 
        task_id="egress-sandbox-e2e"
    )

    # Verify execution succeeded in the real sandbox
    assert result["return_code"] == 0
    assert result["stdout"].strip() == "sandbox-egress-test-42"
    assert result["provider"] == "bubblewrap"
    assert result["timed_out"] is False

    # Verify egress decision was recorded BEFORE provider execution (fail-closed ordering)
    events = _egress_events(audit)
    assert len(events) == 1
    event = events[0]
    assert event.action == "model_egress"
    assert event.result == "allowed"
    assert event.network_action == "local"  # Bubblewrap is local provider
    assert event.metadata["payload_sha256"] == hashlib.sha256(code.encode()).hexdigest()
    # Critical: audit contains digest, not the actual payload
    assert "sandbox-egress-test-42" not in str(event.metadata)
    assert event.metadata["classifications"] == ["public"]  # Default classification

    # Verify scope check recorded the trusted identity + proper python scope
    scope_checks = [
        e for e in audit.query(AuditQuery(event_type=AuditEventType.PERMISSION_CHECK))
        if e.action == "execute_python"
    ]
    results = [e.result for e in scope_checks]
    assert "scope_check_passed" in results
    assert "allowed" in results

    # Verify execution tool result was recorded after the sandbox ran
    results_events = [
        e for e in audit.query(AuditQuery(event_type=AuditEventType.TOOL_RESULT, task_id="egress-sandbox-e2e"))
    ]
    assert len(results_events) == 1
    assert results_events[0].result == "succeeded"

    # FULL mode with a trusted identity and proper python scope auto-grants:
    # no pending or human-approval events exist for this task.
    task_approvals = audit.query(
        AuditQuery(event_type=AuditEventType.APPROVAL_GRANTED, task_id="egress-sandbox-e2e")
    )
    assert len(task_approvals) == 0
    task_pending = audit.query(
        AuditQuery(event_type=AuditEventType.APPROVAL_REQUIRED, task_id="egress-sandbox-e2e")
    )
    assert len(task_pending) == 0
