# tests/test_provision.py
import json
from unittest.mock import AsyncMock, MagicMock
from agentsh_secure_sandbox.core.types import ExecResult, SecureConfig
from agentsh_secure_sandbox.core.provision import provision, ProvisionResult, SECURITY_MODE_RANK


def _make_mock_adapter(exec_side_effects=None):
    """Create a mock adapter that returns configurable exec results."""
    adapter = MagicMock()
    adapter.write_file = AsyncMock()
    adapter.stop = AsyncMock()
    adapter.file_exists = AsyncMock(return_value=False)

    if exec_side_effects:
        adapter.exec = AsyncMock(side_effect=exec_side_effects)
    else:
        adapter.exec = AsyncMock(return_value=ExecResult(stdout="", stderr="", exit_code=0))
    return adapter


def test_security_mode_ranking():
    assert SECURITY_MODE_RANK["full"] > SECURITY_MODE_RANK["landlock"]
    assert SECURITY_MODE_RANK["landlock"] > SECURITY_MODE_RANK["landlock-only"]
    assert SECURITY_MODE_RANK["landlock-only"] > SECURITY_MODE_RANK["minimal"]


async def test_provision_running_strategy():
    """install_strategy='running' reads session from env and defaults to 'full'."""

    def exec_side_effect(cmd, args=None, opts=None):
        full = f"{cmd} {' '.join(args or [])}"
        if "AGENTSH_SESSION_ID" in full:
            return ExecResult(stdout="existing-session-456", stderr="", exit_code=0)
        if "health" in full or ("curl" in full and "18080" in full):
            return ExecResult(stdout="ok", stderr="", exit_code=0)
        return ExecResult(stdout="", stderr="", exit_code=0)

    adapter = _make_mock_adapter()
    adapter.exec = AsyncMock(side_effect=exec_side_effect)
    cfg = SecureConfig(install_strategy="running")
    result = await provision(adapter, cfg)
    assert result.passthrough is True
    assert result.session_id == "existing-session-456"
    assert result.security_mode == "full"


async def test_provision_running_strategy_with_session_id():
    """install_strategy='running' uses explicit session_id from config."""

    def exec_side_effect(cmd, args=None, opts=None):
        full = f"{cmd} {' '.join(args or [])}"
        if "health" in full or ("curl" in full and "18080" in full):
            return ExecResult(stdout="ok", stderr="", exit_code=0)
        return ExecResult(stdout="", stderr="", exit_code=0)

    adapter = _make_mock_adapter()
    adapter.exec = AsyncMock(side_effect=exec_side_effect)
    cfg = SecureConfig(install_strategy="running", session_id="explicit-789")
    result = await provision(adapter, cfg)
    assert result.session_id == "explicit-789"
    assert result.security_mode == "full"


async def test_provision_running_strategy_security_mode_override():
    """install_strategy='running' respects securityMode override."""

    def exec_side_effect(cmd, args=None, opts=None):
        full = f"{cmd} {' '.join(args or [])}"
        if "AGENTSH_SESSION_ID" in full:
            return ExecResult(stdout="sess-123", stderr="", exit_code=0)
        if "health" in full or ("curl" in full and "18080" in full):
            return ExecResult(stdout="ok", stderr="", exit_code=0)
        return ExecResult(stdout="", stderr="", exit_code=0)

    adapter = _make_mock_adapter()
    adapter.exec = AsyncMock(side_effect=exec_side_effect)
    cfg = SecureConfig(install_strategy="running", security_mode="landlock")
    result = await provision(adapter, cfg)
    assert result.security_mode == "landlock"


async def test_provision_result_has_session_id():
    """Basic smoke test — provision returns a ProvisionResult."""
    detect_json = json.dumps({"security_mode": "full"})
    session_json = json.dumps({"session_id": "test-session-123"})

    def exec_side_effect(cmd, args=None, opts=None):
        full = f"{cmd} {' '.join(args or [])}"
        if "uname" in full:
            return ExecResult(stdout="x86_64", stderr="", exit_code=0)
        if "detect" in full:
            return ExecResult(stdout=detect_json, stderr="", exit_code=0)
        if "health" in full or "curl" in full and "18080" in full:
            return ExecResult(stdout="ok", stderr="", exit_code=0)
        if "session" in full and "create" in full:
            return ExecResult(stdout=session_json, stderr="", exit_code=0)
        return ExecResult(stdout="", stderr="", exit_code=0)

    adapter = _make_mock_adapter()
    adapter.exec = AsyncMock(side_effect=exec_side_effect)
    adapter.file_exists = AsyncMock(return_value=True)  # binary exists for preinstalled
    cfg = SecureConfig(install_strategy="preinstalled", skip_integrity_check=True)
    result = await provision(adapter, cfg)
    assert isinstance(result, ProvisionResult)
    assert isinstance(result.session_id, str)
    assert result.security_mode in ("full", "landlock", "landlock-only", "minimal")


def test_security_mode_ranking_includes_ptrace():
    assert SECURITY_MODE_RANK["full"] > SECURITY_MODE_RANK["ptrace"]
    assert SECURITY_MODE_RANK["ptrace"] > SECURITY_MODE_RANK["landlock"]


async def test_provision_skip_shim():
    """When skip_shim=True, shell shim installation is skipped."""
    detect_json = json.dumps({"security_mode": "ptrace"})
    session_json = json.dumps({"session_id": "sess-ptrace"})

    shim_called = False

    def exec_side_effect(cmd, args=None, opts=None):
        nonlocal shim_called
        full = f"{cmd} {' '.join(args or [])}"
        if "shim" in full and "install-shell" in full:
            shim_called = True
            return ExecResult(stdout="", stderr="", exit_code=0)
        if "uname" in full:
            return ExecResult(stdout="x86_64", stderr="", exit_code=0)
        if "detect" in full:
            return ExecResult(stdout=detect_json, stderr="", exit_code=0)
        if "health" in full or ("curl" in full and "18080" in full):
            return ExecResult(stdout="ok", stderr="", exit_code=0)
        if "session" in full and "create" in full:
            return ExecResult(stdout=session_json, stderr="", exit_code=0)
        return ExecResult(stdout="", stderr="", exit_code=0)

    adapter = _make_mock_adapter()
    adapter.exec = AsyncMock(side_effect=exec_side_effect)
    adapter.file_exists = AsyncMock(return_value=True)
    cfg = SecureConfig(
        install_strategy="preinstalled",
        skip_integrity_check=True,
        skip_shim=True,
    )
    result = await provision(adapter, cfg)
    assert result.session_id == "sess-ptrace"
    assert result.security_mode == "ptrace"
    assert shim_called is False
