"""
Tests for Phase 86: Zero-Trust Execution — Sandbox Foreign Agents.

Covers: context creation, tool access checks, sandboxed execution,
trust promotion/demotion, audit trail, policy retrieval, and edge cases.
"""

import json
import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect all zero-trust storage to tmp_path."""
    zt_dir = tmp_path / "zero_trust"
    ctx_dir = zt_dir / "contexts"
    ctx_dir.mkdir(parents=True, exist_ok=True)

    import brynq.zero_trust_exec as zt
    monkeypatch.setattr(zt, "ZT_DIR", zt_dir)
    monkeypatch.setattr(zt, "CONTEXTS_DIR", ctx_dir)
    monkeypatch.setattr(zt, "AUDIT_FILE", zt_dir / "audit.jsonl")


# ── 1. Context Creation ─────────────────────────────────────────────────


class TestCreateContext:
    def test_create_default_untrusted(self):
        from brynq.zero_trust_exec import create_execution_context
        ctx = create_execution_context("agent-a")
        assert ctx["agent_id"] == "agent-a"
        assert ctx["trust_level"] == "untrusted"
        assert ctx["can_write"] is False
        assert ctx["can_execute"] is False
        assert ctx["total_calls"] == 0

    def test_create_with_trust_level(self):
        from brynq.zero_trust_exec import create_execution_context
        ctx = create_execution_context("agent-b", trust_level="verified")
        assert ctx["trust_level"] == "verified"
        assert ctx["can_write"] is True
        assert ctx["can_execute"] is True
        assert "*" in ctx["allowed_tools"]

    def test_create_with_custom_allowed(self):
        from brynq.zero_trust_exec import create_execution_context
        ctx = create_execution_context("agent-c", allowed_tools=["tool_x", "tool_y"])
        assert ctx["allowed_tools"] == ["tool_x", "tool_y"]

    def test_create_with_custom_denied(self):
        from brynq.zero_trust_exec import create_execution_context
        ctx = create_execution_context("agent-d", trust_level="admin",
                                       denied_tools=["dangerous_tool"])
        assert "dangerous_tool" in ctx["denied_tools"]

    def test_create_invalid_trust_level(self):
        from brynq.zero_trust_exec import create_execution_context
        result = create_execution_context("agent-e", trust_level="superuser")
        assert "error" in result

    def test_create_overwrites_existing(self):
        from brynq.zero_trust_exec import create_execution_context, get_trust_level
        create_execution_context("agent-f", trust_level="untrusted")
        create_execution_context("agent-f", trust_level="basic")
        assert get_trust_level("agent-f") == "basic"


# ── 2. Tool Access Checks ───────────────────────────────────────────────


class TestCheckToolAccess:
    def test_allowed_tool(self):
        from brynq.zero_trust_exec import create_execution_context, check_tool_access
        create_execution_context("agent-g")
        allowed, reason = check_tool_access("agent-g", "broker_read")
        assert allowed is True

    def test_denied_tool_not_in_list(self):
        from brynq.zero_trust_exec import create_execution_context, check_tool_access
        create_execution_context("agent-h")
        allowed, reason = check_tool_access("agent-h", "broker_post")
        assert allowed is False
        assert "not in allowed list" in reason

    def test_explicitly_denied_tool(self):
        from brynq.zero_trust_exec import create_execution_context, check_tool_access
        create_execution_context("agent-i", trust_level="verified")
        allowed, reason = check_tool_access("agent-i", "broker_pardon")
        assert allowed is False
        assert "explicitly denied" in reason

    def test_wildcard_allows_all(self):
        from brynq.zero_trust_exec import create_execution_context, check_tool_access
        create_execution_context("agent-j", trust_level="admin")
        allowed, reason = check_tool_access("agent-j", "any_tool_name")
        assert allowed is True
        assert "Wildcard" in reason

    def test_no_context_returns_false(self):
        from brynq.zero_trust_exec import check_tool_access
        allowed, reason = check_tool_access("nonexistent", "broker_read")
        assert allowed is False
        assert "No execution context" in reason


# ── 3. Sandboxed Execution ──────────────────────────────────────────────


class TestExecuteSandboxed:
    def test_allowed_execution(self):
        from brynq.zero_trust_exec import create_execution_context, execute_sandboxed
        create_execution_context("agent-k")
        result = execute_sandboxed("agent-k", "broker_read", {"channel": "gen"})
        assert result["allowed"] is True
        assert result["tool"] == "broker_read"
        assert result["arguments"] == {"channel": "gen"}
        assert "exec_id" in result

    def test_denied_execution(self):
        from brynq.zero_trust_exec import create_execution_context, execute_sandboxed
        create_execution_context("agent-l")
        result = execute_sandboxed("agent-l", "broker_post")
        assert "error" in result
        assert result["allowed"] is False

    def test_no_context_execution(self):
        from brynq.zero_trust_exec import execute_sandboxed
        result = execute_sandboxed("ghost", "broker_read")
        assert "error" in result

    def test_execution_increments_counters(self):
        from brynq.zero_trust_exec import (create_execution_context,
                                            execute_sandboxed, _load_ctx)
        create_execution_context("agent-m")
        execute_sandboxed("agent-m", "broker_read")
        execute_sandboxed("agent-m", "broker_read")
        execute_sandboxed("agent-m", "broker_post")  # denied
        ctx = _load_ctx("agent-m")
        assert ctx["total_calls"] == 3
        assert ctx["total_denied"] == 1

    def test_execution_default_arguments(self):
        from brynq.zero_trust_exec import create_execution_context, execute_sandboxed
        create_execution_context("agent-n")
        result = execute_sandboxed("agent-n", "broker_read")
        assert result["arguments"] == {}


# ── 4. Trust Level Queries ──────────────────────────────────────────────


class TestGetTrustLevel:
    def test_known_agent(self):
        from brynq.zero_trust_exec import create_execution_context, get_trust_level
        create_execution_context("agent-o", trust_level="trusted")
        assert get_trust_level("agent-o") == "trusted"

    def test_unknown_agent(self):
        from brynq.zero_trust_exec import get_trust_level
        assert get_trust_level("nobody") == "unknown"


# ── 5. Trust Promotion ──────────────────────────────────────────────────


class TestPromoteTrust:
    def test_valid_promotion(self):
        from brynq.zero_trust_exec import (create_execution_context,
                                            promote_trust, get_trust_level)
        create_execution_context("agent-p")
        result = promote_trust("agent-p", "basic", reason="good behavior")
        assert result["old_level"] == "untrusted"
        assert result["new_level"] == "basic"
        assert get_trust_level("agent-p") == "basic"

    def test_promotion_updates_policy(self):
        from brynq.zero_trust_exec import (create_execution_context,
                                            promote_trust, _load_ctx)
        create_execution_context("agent-q")
        promote_trust("agent-q", "verified")
        ctx = _load_ctx("agent-q")
        assert ctx["can_execute"] is True
        assert "*" in ctx["allowed_tools"]

    def test_cannot_promote_to_lower(self):
        from brynq.zero_trust_exec import create_execution_context, promote_trust
        create_execution_context("agent-r", trust_level="trusted")
        result = promote_trust("agent-r", "basic")
        assert "error" in result

    def test_cannot_promote_to_same(self):
        from brynq.zero_trust_exec import create_execution_context, promote_trust
        create_execution_context("agent-s", trust_level="basic")
        result = promote_trust("agent-s", "basic")
        assert "error" in result

    def test_promote_nonexistent(self):
        from brynq.zero_trust_exec import promote_trust
        result = promote_trust("ghost", "admin")
        assert "error" in result

    def test_promote_invalid_level(self):
        from brynq.zero_trust_exec import create_execution_context, promote_trust
        create_execution_context("agent-t")
        result = promote_trust("agent-t", "superadmin")
        assert "error" in result


# ── 6. Trust Demotion ───────────────────────────────────────────────────


class TestDemoteTrust:
    def test_valid_demotion(self):
        from brynq.zero_trust_exec import (create_execution_context,
                                            demote_trust, get_trust_level)
        create_execution_context("agent-u", trust_level="trusted")
        result = demote_trust("agent-u", "basic", reason="misbehavior")
        assert result["old_level"] == "trusted"
        assert result["new_level"] == "basic"
        assert get_trust_level("agent-u") == "basic"

    def test_cannot_demote_to_higher(self):
        from brynq.zero_trust_exec import create_execution_context, demote_trust
        create_execution_context("agent-v", trust_level="basic")
        result = demote_trust("agent-v", "admin")
        assert "error" in result

    def test_cannot_demote_to_same(self):
        from brynq.zero_trust_exec import create_execution_context, demote_trust
        create_execution_context("agent-w", trust_level="verified")
        result = demote_trust("agent-w", "verified")
        assert "error" in result

    def test_demote_nonexistent(self):
        from brynq.zero_trust_exec import demote_trust
        result = demote_trust("ghost", "untrusted")
        assert "error" in result


# ── 7. Audit Trail ──────────────────────────────────────────────────────


class TestAudit:
    def test_audit_records_creation(self):
        from brynq.zero_trust_exec import create_execution_context, get_execution_audit
        create_execution_context("agent-x")
        entries = get_execution_audit(agent_id="agent-x")
        assert any(e["event"] == "context_created" for e in entries)

    def test_audit_records_execution(self):
        from brynq.zero_trust_exec import (create_execution_context,
                                            execute_sandboxed, get_execution_audit)
        create_execution_context("agent-y")
        execute_sandboxed("agent-y", "broker_read")
        entries = get_execution_audit(agent_id="agent-y")
        assert any(e["event"] == "exec_allowed" for e in entries)

    def test_audit_records_denial(self):
        from brynq.zero_trust_exec import (create_execution_context,
                                            execute_sandboxed, get_execution_audit)
        create_execution_context("agent-z")
        execute_sandboxed("agent-z", "broker_post")
        entries = get_execution_audit(agent_id="agent-z")
        assert any(e["event"] == "exec_denied" for e in entries)

    def test_audit_limit(self):
        from brynq.zero_trust_exec import (create_execution_context,
                                            execute_sandboxed, get_execution_audit)
        create_execution_context("agent-lim")
        for _ in range(10):
            execute_sandboxed("agent-lim", "broker_read")
        entries = get_execution_audit(agent_id="agent-lim", limit=5)
        assert len(entries) == 5

    def test_audit_all_agents(self):
        from brynq.zero_trust_exec import create_execution_context, get_execution_audit
        create_execution_context("a1")
        create_execution_context("a2")
        entries = get_execution_audit()
        assert len(entries) >= 2

    def test_audit_records_promotion(self):
        from brynq.zero_trust_exec import (create_execution_context,
                                            promote_trust, get_execution_audit)
        create_execution_context("agent-promo")
        promote_trust("agent-promo", "verified")
        entries = get_execution_audit(agent_id="agent-promo")
        assert any(e["event"] == "trust_promoted" for e in entries)

    def test_audit_records_demotion(self):
        from brynq.zero_trust_exec import (create_execution_context,
                                            demote_trust, get_execution_audit)
        create_execution_context("agent-demo", trust_level="admin")
        demote_trust("agent-demo", "basic")
        entries = get_execution_audit(agent_id="agent-demo")
        assert any(e["event"] == "trust_demoted" for e in entries)


# ── 8. Policy Retrieval ─────────────────────────────────────────────────


class TestGetPolicy:
    def test_valid_policy(self):
        from brynq.zero_trust_exec import get_policy
        p = get_policy("admin")
        assert p["trust_level"] == "admin"
        assert "*" in p["allowed_tools"]
        assert p["max_calls_per_minute"] == 0

    def test_all_levels_have_policy(self):
        from brynq.zero_trust_exec import get_policy, TRUST_LEVELS
        for level in TRUST_LEVELS:
            p = get_policy(level)
            assert "error" not in p
            assert "description" in p

    def test_invalid_level_policy(self):
        from brynq.zero_trust_exec import get_policy
        p = get_policy("superuser")
        assert "error" in p
