"""Tests for runtime_sandbox.py — security primitives for in-process agents."""

import tempfile
import time
from pathlib import Path

import pytest

from brynq.runtime_sandbox import (
    AgentSecurityContext,
    FileSandbox,
    ResourceLimiter,
    get_safe_env,
    get_sanitized_env,
    sanitize_message,
    scan_for_credentials,
)


# ── FileSandbox ──────────────────────────────────────────────────────────


class TestFileSandbox:
    def setup_method(self):
        self.root = tempfile.mkdtemp()
        self.sb = FileSandbox(self.root)

    def test_read_inside_root(self):
        assert self.sb.check_read(self.root + "/test.py") is True

    def test_read_root_itself(self):
        assert self.sb.check_read(self.root) is True

    def test_read_outside_root(self):
        assert self.sb.check_read("/etc/passwd") is False
        assert self.sb.check_read("C:\\Windows\\System32") is False

    def test_write_inside_root(self):
        assert self.sb.check_write(self.root + "/output.txt") is True

    def test_write_outside_root(self):
        assert self.sb.check_write("/tmp/evil.txt") is False

    def test_delete_root_blocked(self):
        assert self.sb.check_delete(self.root) is False

    def test_delete_git_blocked(self):
        assert self.sb.check_delete(self.root + "/.git/HEAD") is False

    def test_delete_broker_state_blocked(self):
        assert self.sb.check_delete(self.root + "/state/broker/channels/x.jsonl") is False

    def test_delete_normal_file_allowed(self):
        assert self.sb.check_delete(self.root + "/temp.txt") is True

    def test_read_extra_dirs(self):
        extra = tempfile.mkdtemp()
        sb = FileSandbox(self.root, read_extra=[extra])
        assert sb.check_read(extra + "/data.json") is True
        assert sb.check_write(extra + "/data.json") is False  # read-only

    def test_deny_list_blocks_vault(self):
        home = str(Path.home())
        assert self.sb.check_read(home + "/.brynq/vault.json") is False

    def test_deny_list_blocks_ssh(self):
        home = str(Path.home())
        assert self.sb.check_read(home + "/.ssh/id_rsa") is False

    def test_safe_open_read_blocked(self):
        with pytest.raises(PermissionError):
            self.sb.safe_open("/etc/shadow", "r")

    def test_safe_open_write_blocked(self):
        with pytest.raises(PermissionError):
            self.sb.safe_open("/tmp/hack.txt", "w")

    def test_safe_open_read_allowed(self):
        # Create a real file to open
        p = Path(self.root) / "readable.txt"
        p.write_text("hello")
        f = self.sb.safe_open(str(p), "r")
        assert f.read() == "hello"
        f.close()


# ── Credential Scanner ───────────────────────────────────────────────────


class TestCredentialScanner:
    """Build fake credential strings at runtime so no scanner-triggerable
    patterns exist in the source file."""

    @staticmethod
    def _fake(prefix: str, body: str = "x" * 24) -> str:
        return prefix + body

    def test_clean_text(self):
        assert scan_for_credentials("hello world") == []

    def test_detect_openai_key(self):
        hits = scan_for_credentials("my key is " + self._fake("sk-"))
        assert len(hits) > 0

    def test_detect_stripe_live(self):
        hits = scan_for_credentials(self._fake("sk_live_"))
        assert len(hits) > 0

    def test_detect_aws_key(self):
        hits = scan_for_credentials(self._fake("AKIA", "IOSFODNN7EXAMPLE"))
        assert len(hits) > 0

    def test_detect_github_token(self):
        hits = scan_for_credentials(self._fake("ghp_", "A" * 36))
        assert len(hits) > 0

    def test_detect_private_key(self):
        hits = scan_for_credentials("-----BEGIN RSA PRIVATE KEY-----")
        assert len(hits) > 0

    def test_sanitize_redacts(self):
        dirty = "token: " + self._fake("sk-")
        clean = sanitize_message(dirty)
        assert "[REDACTED]" in clean
        assert "sk-" not in clean

    def test_sanitize_clean_unchanged(self):
        msg = "this is a normal message"
        assert sanitize_message(msg) == msg


# ── Environment Isolation ────────────────────────────────────────────────


class TestEnvironmentIsolation:
    def test_safe_env_blocks_sensitive(self):
        assert get_safe_env("STRIPE_API_KEY") is None
        assert get_safe_env("ANTHROPIC_API_KEY") is None

    def test_safe_env_allows_normal(self):
        import os
        os.environ["TEST_SAFE_VAR"] = "hello"
        assert get_safe_env("TEST_SAFE_VAR") == "hello"
        del os.environ["TEST_SAFE_VAR"]

    def test_sanitized_env_redacts(self):
        import os
        os.environ["STRIPE_API_KEY"] = "sk_test_secret"
        env = get_sanitized_env()
        assert env.get("STRIPE_API_KEY") == "[REDACTED]"
        del os.environ["STRIPE_API_KEY"]


# ── Resource Limiter ─────────────────────────────────────────────────────


class TestResourceLimiter:
    def test_within_limits(self):
        rl = ResourceLimiter(max_messages_per_min=5)
        for _ in range(5):
            assert rl.record("messages") is True

    def test_exceeds_limit(self):
        rl = ResourceLimiter(max_messages_per_min=3)
        for _ in range(3):
            rl.record("messages")
        assert rl.record("messages") is False

    def test_task_slots(self):
        rl = ResourceLimiter(max_concurrent_tasks=2)
        assert rl.record("tasks") is True
        assert rl.record("tasks") is True
        assert rl.record("tasks") is False

    def test_release_task(self):
        rl = ResourceLimiter(max_concurrent_tasks=1)
        rl.record("tasks")
        assert rl.check("tasks") is False
        rl.release_task()
        assert rl.check("tasks") is True

    def test_unknown_resource_allowed(self):
        rl = ResourceLimiter()
        assert rl.check("nonexistent") is True

    def test_get_usage(self):
        rl = ResourceLimiter(max_messages_per_min=10)
        rl.record("messages")
        rl.record("messages")
        usage = rl.get_usage()
        assert usage["messages_per_min"] == 2
        assert "limits" in usage


# ── Agent Security Context ───────────────────────────────────────────────


class TestAgentSecurityContext:
    def setup_method(self):
        self.root = tempfile.mkdtemp()

    def test_create_context(self):
        ctx = AgentSecurityContext("agent-1", self.root)
        assert ctx.agent_id == "agent-1"
        assert ctx.trust_level == "contributor"

    def test_clean_message_allowed(self):
        ctx = AgentSecurityContext("agent-2", self.root)
        result = ctx.check_message("hello world")
        assert result["allowed"] is True

    def test_credential_message_blocked(self):
        ctx = AgentSecurityContext("agent-3", self.root)
        fake_key = "sk-" + "x" * 24
        result = ctx.check_message("my key: " + fake_key)
        assert result["allowed"] is False
        assert "sanitized" in result

    def test_file_read_inside_sandbox(self):
        ctx = AgentSecurityContext("agent-4", self.root)
        result = ctx.check_file_access(self.root + "/file.py", "r")
        assert result["allowed"] is True

    def test_file_write_outside_sandbox(self):
        ctx = AgentSecurityContext("agent-5", self.root)
        result = ctx.check_file_access("/tmp/hack.py", "w")
        assert result["allowed"] is False

    def test_task_accept_within_limits(self):
        ctx = AgentSecurityContext("agent-6", self.root)
        result = ctx.check_task_accept()
        assert result["allowed"] is True

    def test_trust_level_affects_limits(self):
        intern_ctx = AgentSecurityContext("intern", self.root, trust_level="intern")
        core_ctx = AgentSecurityContext("core", self.root, trust_level="core")
        assert intern_ctx.limiter.limits["messages"] < core_ctx.limiter.limits["messages"]

    def test_violations_tracked(self):
        ctx = AgentSecurityContext("agent-7", self.root)
        ctx.check_message("sk-" + "x" * 24)
        assert len(ctx.violations) == 1
        assert ctx.violations[0]["type"] == "credential_leak"

    def test_get_status(self):
        ctx = AgentSecurityContext("agent-8", self.root)
        status = ctx.get_status()
        assert status["agent_id"] == "agent-8"
        assert "resource_usage" in status
        assert "violation_count" in status
