"""exe.dev end-to-end tests — mirrors exe-e2e-runner.ts."""

from __future__ import annotations

import time

import pytest
import pytest_asyncio

from tests.e2e.conftest import ENV, skip_no_exe

from agentsh_secure_sandbox import SecuredSandbox, secure_sandbox
from agentsh_secure_sandbox.adapters import exe, exe_defaults
from agentsh_secure_sandbox.core.types import ExecOpts

pytestmark = [pytest.mark.e2e, pytest.mark.timeout(600), skip_no_exe, pytest.mark.asyncio(loop_scope="module")]

VM_NAME = ENV.get("EXE_VM_NAME", "agentsh-e2e")

# ─── Module-scoped setup / teardown ────────────────────────


@pytest_asyncio.fixture(scope="module", loop_scope="module")
async def adapter():  # type: ignore[misc]
    """Create an exe.dev adapter — VM must already exist."""
    return exe(VM_NAME)


@pytest_asyncio.fixture(scope="module", loop_scope="module")
async def secured(adapter) -> SecuredSandbox:  # type: ignore[misc]
    """Provision a secured sandbox via the exe.dev adapter."""
    sec = await secure_sandbox(adapter, exe_defaults())

    yield sec  # type: ignore[misc]

    await sec.stop()


# ─── Adapter tests ───────────────────────────────────────────


class TestExeAdapter:
    async def test_exec_simple_command(self, adapter) -> None:
        result = await adapter.exec("echo", ["hello"])
        assert result.exit_code == 0
        assert result.stdout.strip() == "hello"

    async def test_exec_nonzero_exit(self, adapter) -> None:
        result = await adapter.exec("ls", ["/nonexistent-path-xyz"])
        assert result.exit_code != 0

    async def test_exec_with_env_vars(self, adapter) -> None:
        result = await adapter.exec("sh", ["-c", "echo $TEST_VAR"],
                                    ExecOpts(env={"TEST_VAR": "exe-e2e"}))
        assert result.exit_code == 0
        assert result.stdout.strip() == "exe-e2e"

    async def test_exec_with_cwd(self, adapter) -> None:
        result = await adapter.exec("pwd", opts=ExecOpts(cwd="/tmp"))
        assert result.exit_code == 0
        assert result.stdout.strip() == "/tmp"

    async def test_write_read_roundtrip(self, adapter) -> None:
        await adapter.write_file("/tmp/exe-e2e-test.txt", "hello from e2e")
        content = await adapter.read_file("/tmp/exe-e2e-test.txt")
        assert content == "hello from e2e"

    async def test_write_read_special_chars(self, adapter) -> None:
        content = 'line1\nline2\ttab\n"quotes" & <brackets>'
        await adapter.write_file("/tmp/exe-e2e-special.txt", content)
        read = await adapter.read_file("/tmp/exe-e2e-special.txt")
        assert read == content

    async def test_readfile_raises_on_missing(self, adapter) -> None:
        with pytest.raises(Exception, match="readFile failed"):
            await adapter.read_file("/tmp/exe-e2e-nonexistent-xyz.txt")

    async def test_file_exists_true(self, adapter) -> None:
        await adapter.write_file("/tmp/exe-e2e-exists.txt", "x")
        assert await adapter.file_exists("/tmp/exe-e2e-exists.txt") is True

    async def test_file_exists_false(self, adapter) -> None:
        assert await adapter.file_exists("/tmp/exe-e2e-nonexistent-xyz") is False

    async def test_detached_exec(self, adapter) -> None:
        start = time.time()
        result = await adapter.exec("sleep", ["10"], ExecOpts(detached=True))
        elapsed = time.time() - start
        assert result.exit_code == 0
        assert elapsed < 5.0, f"detached took too long: {elapsed:.1f}s"

    async def test_stop_is_noop(self, adapter) -> None:
        await adapter.stop()
        # VM should still be accessible
        result = await adapter.exec("echo", ["still-alive"])
        assert result.exit_code == 0
        assert result.stdout.strip() == "still-alive"


# ─── Smoke tests ──────────────────────────────────────────


class TestExeSmoke:
    async def test_provisions_session_id(self, secured: SecuredSandbox) -> None:
        assert secured.session_id
        assert isinstance(secured.session_id, str)

    async def test_reports_valid_security_mode(self, secured: SecuredSandbox) -> None:
        assert secured.security_mode in ("full", "ptrace", "landlock", "landlock-only", "minimal")

    async def test_exec_simple_command(self, secured: SecuredSandbox) -> None:
        result = await secured.exec("echo hello")
        assert result.exit_code == 0
        assert result.stdout.strip() == "hello"

    async def test_write_read_roundtrip(self, secured: SecuredSandbox) -> None:
        path = "/root/e2e-test-file.txt"
        content = f"e2e-roundtrip-{int(time.time() * 1000)}"

        write_result = await secured.write_file(path, content)
        assert write_result.success is True

        read_result = await secured.read_file(path)
        assert read_result.success is True
        if read_result.success:
            assert read_result.content.strip() == content


# ─── Policy enforcement ───────────────────────────────────


class TestExePolicy:
    async def test_blocks_sudo(self, secured: SecuredSandbox) -> None:
        if secured.security_mode == "full":
            result = await secured.exec("sudo whoami")
            assert result.exit_code != 0

    async def test_denies_env_write(self, secured: SecuredSandbox) -> None:
        if secured.security_mode == "full":
            result = await secured.write_file("/root/.env", "SECRET=leaked")
            assert result.success is False

    async def test_allows_workspace_write(self, secured: SecuredSandbox) -> None:
        result = await secured.write_file("/root/allowed-file.txt", "allowed")
        assert result.success is True

    async def test_allows_npm_registry(self, secured: SecuredSandbox) -> None:
        result = await secured.exec(
            'curl -s -o /dev/null -w "%{http_code}" https://registry.npmjs.org/'
            ' || echo "no-curl"'
        )
        if "no-curl" not in result.stdout:
            assert result.stdout.strip() == "200"

    async def test_blocks_unauthorized_network(self, secured: SecuredSandbox) -> None:
        result = await secured.exec(
            "curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5"
            " https://evil.example.com 2>&1"
        )
        assert result.stdout.strip() != "200"

    async def test_denies_ssh_keys(self, secured: SecuredSandbox) -> None:
        if secured.security_mode == "full":
            result = await secured.exec("cat ~/.ssh/id_rsa 2>&1")
            assert result.exit_code != 0

    async def test_blocks_nc(self, secured: SecuredSandbox) -> None:
        if secured.security_mode == "full":
            result = await secured.exec("nc -h 2>&1 || true")
            assert result.exit_code != 0 or "blocked" in result.stderr or "denied" in result.stderr

    async def test_blocks_kill(self, secured: SecuredSandbox) -> None:
        if secured.security_mode == "full":
            result = await secured.exec("kill -0 1 2>&1")
            assert result.exit_code != 0

    async def test_blocks_shelley(self, secured: SecuredSandbox) -> None:
        if secured.security_mode == "full":
            result = await secured.exec("shelley --help 2>&1 || true")
            assert result.exit_code != 0 or "blocked" in result.stderr or "denied" in result.stderr

    async def test_blocks_iptables(self, secured: SecuredSandbox) -> None:
        if secured.security_mode == "full":
            result = await secured.exec("iptables -L 2>&1 || true")
            assert result.exit_code != 0 or "blocked" in result.stderr or "denied" in result.stderr

    async def test_filters_sensitive_env_vars(self, secured: SecuredSandbox) -> None:
        result = await secured.exec('bash -c "echo $SECRET_KEY"')
        assert result.stdout.strip() == ""
