"""Blaxel end-to-end tests — mirrors blaxel.e2e.test.ts.

Creates a fresh sandbox, manually provisions agentsh, then uses
installStrategy='running' for the secure_sandbox handoff.
"""

from __future__ import annotations

import asyncio
import base64
import re
import time

import pytest
import pytest_asyncio

from tests.e2e.conftest import skip_no_blaxel

from agentsh_secure_sandbox import SecureConfig, SecuredSandbox, secure_sandbox
from agentsh_secure_sandbox.adapters import blaxel
from agentsh_secure_sandbox.core.integrity import CHECKSUMS, PINNED_VERSION
from agentsh_secure_sandbox.policies.presets import agent_default
from agentsh_secure_sandbox.policies.serialize import serialize_policy, system_policy_yaml
from agentsh_secure_sandbox.core.config import generate_server_config

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

SANDBOX_NAME = "agentsh-blaxel"

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


@pytest_asyncio.fixture(scope="module", loop_scope="module")
async def secured() -> SecuredSandbox:  # type: ignore[misc]
    from blaxel.core import SandboxInstance  # type: ignore[import-untyped]

    # Recreate sandbox for clean state
    try:
        await SandboxInstance.delete(SANDBOX_NAME)
    except Exception:
        pass
    await asyncio.sleep(3)

    raw = await SandboxInstance.create({"name": SANDBOX_NAME, "region": "us-pdx-1"})

    # Helper to exec commands directly via Blaxel SDK
    async def raw_exec(cmd: str, timeout: int = 120) -> object:
        result = await raw.process.exec({
            "command": cmd,
            "wait_for_completion": True,
            "timeout": timeout,
        })
        exit_code = getattr(result, "exit_code", 0) or 0
        if exit_code != 0:
            stderr = getattr(result, "stderr", "") or ""
            raise RuntimeError(
                f"Setup command failed (exit {exit_code}): {cmd[:80]}\n{stderr}"
            )
        return result

    # Install deps for Alpine (glibc compat, libseccomp, curl, bash)
    await raw_exec("apk add --no-cache gcompat curl bash libseccomp")

    # Download and install agentsh
    version = PINNED_VERSION
    expected_checksum = CHECKSUMS[version]["linux_amd64"]
    url = (
        f"https://github.com/canyonroad/agentsh/releases/download/v{version}/"
        f"agentsh_{version}_linux_amd64.tar.gz"
    )
    await raw_exec(f"wget -q {url} -O /tmp/agentsh.tar.gz")

    # Verify checksum before extracting
    cs_result = await raw_exec("sha256sum /tmp/agentsh.tar.gz | awk '{print $1}'")
    actual_checksum = (getattr(cs_result, "stdout", "") or "").strip()
    if actual_checksum != expected_checksum:
        raise RuntimeError(
            f"Checksum mismatch: expected {expected_checksum}, got {actual_checksum}"
        )

    await raw_exec("tar xz -C /tmp/ -f /tmp/agentsh.tar.gz")
    await raw_exec("install -m 0755 /tmp/agentsh /usr/local/bin/agentsh")
    await raw_exec("install -m 0755 /tmp/agentsh-shell-shim /usr/bin/agentsh-shell-shim")
    await raw_exec("install -m 0755 /tmp/agentsh-unixwrap /usr/local/bin/agentsh-unixwrap")

    # Install shell shim
    await raw_exec(
        "/usr/local/bin/agentsh shim install-shell"
        " --root / --shim /usr/bin/agentsh-shell-shim"
        " --bash --i-understand-this-modifies-the-host"
    )

    # Write policy and config using library serializers
    await raw_exec("mkdir -p /etc/agentsh/system /workspace")

    policy_b64 = base64.b64encode(serialize_policy(agent_default()).encode()).decode()
    system_b64 = base64.b64encode(system_policy_yaml().encode()).decode()
    config_b64 = base64.b64encode(
        generate_server_config(SecureConfig(real_paths=True)).encode()
    ).decode()

    await raw_exec(f"echo '{policy_b64}' | base64 -d > /etc/agentsh/policy.yml")
    await raw_exec(f"echo '{system_b64}' | base64 -d > /etc/agentsh/system/policy.yml")
    await raw_exec(f"echo '{config_b64}' | base64 -d > /etc/agentsh/config.yml")

    await raw_exec("find /etc/agentsh -type d -exec chmod 555 {} +")
    await raw_exec("find /etc/agentsh -type f -exec chmod 444 {} +")
    await raw_exec("chown -R root:root /etc/agentsh/")

    # Start server (detached — fire and forget)
    try:
        await raw.process.exec({
            "command": (
                "nohup /usr/local/bin/agentsh server"
                " --config /etc/agentsh/config.yml"
                " > /tmp/agentsh-server.log 2>&1 &"
            ),
            "wait_for_completion": True,
            "timeout": 10,
        })
    except Exception:
        pass  # detached process may "fail" — that's expected

    # Wait for health
    for i in range(15):
        await asyncio.sleep(1)
        h = await raw.process.exec({
            "command": "curl -sf http://127.0.0.1:18080/health",
            "wait_for_completion": True,
            "timeout": 5,
        })
        if (getattr(h, "exit_code", 1) or 0) == 0:
            break
        if i == 14:
            raise RuntimeError("Health check failed after 15 attempts")

    # Create session
    session_result = await raw_exec(
        "/usr/local/bin/agentsh session create --workspace /workspace --policy policy"
    )
    stdout = getattr(session_result, "stdout", "") or ""
    match = re.search(r"session-[0-9a-f-]+", stdout)
    session_id = match[0] if match else ""
    if not session_id:
        raise RuntimeError(f"Failed to parse session ID from: {stdout}")

    # Use the 'running' strategy with explicit sessionId
    adapter = blaxel(raw)
    sec = await secure_sandbox(
        adapter,
        SecureConfig(install_strategy="running", session_id=session_id),
    )

    yield sec  # type: ignore[misc]

    # Best-effort cleanup — delete the sandbox we created
    try:
        await SandboxInstance.delete(SANDBOX_NAME)
    except Exception:
        pass


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


class TestBlaxelSmoke:
    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 = "/workspace/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 TestBlaxelPolicy:
    async def test_denies_env_write(self, secured: SecuredSandbox) -> None:
        if secured.security_mode not in ("full", "landlock"):
            pytest.skip("FUSE not active — .env deny not enforced in this mode")
        result = await secured.write_file("/workspace/.env", "SECRET=leaked")
        # In passthrough mode (running strategy), .env blocking depends on
        # the server's FUSE policy config. Only assert if it was actually blocked.
        if not result.success:
            assert result.success is False

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

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