"""Tests for ievo.core.docker — Docker sandbox utilities."""

from __future__ import annotations

import subprocess
from pathlib import Path
from unittest.mock import patch

from ievo.core.agent import AgentPackage, SandboxConfig
from ievo.core.docker import (
    SANDBOX_IMAGE,
    DockerCheckResult,
    DockerStatus,
    build_docker_command,
    build_sandbox_image,
    check_docker,
    image_exists,
)

# ── check_docker ─────────────────────────────────────────────


def test_check_docker_ready() -> None:
    with patch("subprocess.run") as mock:
        mock.return_value = subprocess.CompletedProcess(
            args=[], returncode=0, stdout="24.0.7\n", stderr=""
        )
        result = check_docker()
    assert result.status == DockerStatus.READY
    assert "24.0.7" in result.message


def test_check_docker_not_running() -> None:
    with patch("subprocess.run") as mock:
        mock.return_value = subprocess.CompletedProcess(
            args=[], returncode=1, stdout="", stderr="Cannot connect"
        )
        result = check_docker()
    assert result.status == DockerStatus.NOT_RUNNING
    assert "not running" in result.message


def test_check_docker_not_installed() -> None:
    with patch("subprocess.run", side_effect=FileNotFoundError):
        result = check_docker()
    assert result.status == DockerStatus.NOT_INSTALLED
    assert "not found" in result.message


def test_check_docker_timeout() -> None:
    with patch("subprocess.run", side_effect=subprocess.TimeoutExpired(cmd="docker", timeout=10)):
        result = check_docker()
    assert result.status == DockerStatus.NOT_RUNNING
    assert "timed out" in result.message


# ── image_exists ─────────────────────────────────────────────


def test_image_exists_true() -> None:
    with patch("subprocess.run") as mock:
        mock.return_value = subprocess.CompletedProcess(args=[], returncode=0)
        assert image_exists() is True


def test_image_exists_false() -> None:
    with patch("subprocess.run") as mock:
        mock.return_value = subprocess.CompletedProcess(args=[], returncode=1)
        assert image_exists() is False


def test_image_exists_custom_image() -> None:
    with patch("subprocess.run") as mock:
        mock.return_value = subprocess.CompletedProcess(args=[], returncode=0)
        assert image_exists("custom:v1") is True
        mock.assert_called_once()
        assert "custom:v1" in mock.call_args[0][0]


# ── build_sandbox_image ──────────────────────────────────────


def test_build_sandbox_image_success(tmp_path: Path) -> None:
    with patch("subprocess.run") as mock:
        mock.return_value = subprocess.CompletedProcess(args=[], returncode=0)
        assert build_sandbox_image(tmp_path) is True
        cmd = mock.call_args[0][0]
        assert "docker" in cmd
        assert "build" in cmd
        assert str(tmp_path) in cmd


def test_build_sandbox_image_failure(tmp_path: Path) -> None:
    with patch("subprocess.run") as mock:
        mock.return_value = subprocess.CompletedProcess(args=[], returncode=1)
        assert build_sandbox_image(tmp_path) is False


def test_build_sandbox_image_custom_name(tmp_path: Path) -> None:
    with patch("subprocess.run") as mock:
        mock.return_value = subprocess.CompletedProcess(args=[], returncode=0)
        build_sandbox_image(tmp_path, image="custom:v2")
        cmd = mock.call_args[0][0]
        assert "custom:v2" in cmd


# ── build_docker_command ─────────────────────────────────────


def _make_agent(tmp_path: Path, **sandbox_kwargs: object) -> tuple[Path, Path, AgentPackage]:
    """Create agent directory structure and return (root, agent_dir, pkg)."""
    root = tmp_path / "project"
    root.mkdir()
    agent_dir = tmp_path / "agent"
    agent_dir.mkdir()
    (agent_dir / "ROLE.md").write_text("# Agent\n\nYou are a test agent.\n")
    (agent_dir / "memory").mkdir()
    (agent_dir / "memory" / "CONTEXT.md").write_text("# CONTEXT\n\nSome context.\n")
    (agent_dir / "memory" / "EMPTY.md").write_text("# EMPTY\n\n")  # header-only, skipped

    sandbox = SandboxConfig(**sandbox_kwargs)  # type: ignore[arg-type]
    pkg = AgentPackage(name="test", sandbox=sandbox)
    return root, agent_dir, pkg


def test_build_docker_command_basic(tmp_path: Path) -> None:
    root, agent_dir, pkg = _make_agent(tmp_path)
    cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, None)

    assert cmd[0] == "docker"
    assert "run" in cmd
    assert "--rm" in cmd
    assert SANDBOX_IMAGE in cmd
    assert "--model" in cmd
    assert "sonnet" in cmd
    assert "--system-prompt" in cmd
    assert "--allowedTools" in cmd


def test_build_docker_command_volume_mount(tmp_path: Path) -> None:
    root, agent_dir, pkg = _make_agent(tmp_path)
    cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, None)

    assert "-v" in cmd
    idx = cmd.index("-v")
    assert f"{root}:/workspace" == cmd[idx + 1]


def test_build_docker_command_resource_limits(tmp_path: Path) -> None:
    root, agent_dir, pkg = _make_agent(tmp_path, memory_mb=4096, cpu_limit=3.0)
    cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, None)

    assert "--memory" in cmd
    mem_idx = cmd.index("--memory")
    assert cmd[mem_idx + 1] == "4096m"
    assert "--cpus" in cmd
    cpu_idx = cmd.index("--cpus")
    assert cmd[cpu_idx + 1] == "3.0"


def test_build_docker_command_with_message(tmp_path: Path) -> None:
    root, agent_dir, pkg = _make_agent(tmp_path)
    cmd = build_docker_command(root, agent_dir, pkg, "sonnet", "Hello agent", None)

    assert "-p" in cmd
    assert "Hello agent" in cmd


def test_build_docker_command_with_prompt_file(tmp_path: Path) -> None:
    root, agent_dir, pkg = _make_agent(tmp_path)
    prompt = tmp_path / "prompt.txt"
    prompt.write_text("Do something")
    cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, prompt)

    assert "-p" in cmd
    assert "Do something" in cmd


def test_build_docker_command_network_adds_web_tools(tmp_path: Path) -> None:
    root, agent_dir, pkg = _make_agent(tmp_path, network=True)
    cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, None)

    tools_idx = cmd.index("--allowedTools")
    tools_str = cmd[tools_idx + 1]
    assert "WebFetch" in tools_str
    assert "WebSearch" in tools_str


def test_build_docker_command_no_network_no_web_tools(tmp_path: Path) -> None:
    root, agent_dir, pkg = _make_agent(tmp_path, network=False)
    cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, None)

    tools_idx = cmd.index("--allowedTools")
    tools_str = cmd[tools_idx + 1]
    assert "WebFetch" not in tools_str
    assert "WebSearch" not in tools_str


def test_build_docker_command_network_no_duplicate_tools(tmp_path: Path) -> None:
    root, agent_dir, pkg = _make_agent(
        tmp_path,
        network=True,
        allowed_tools=["Read", "Write", "WebFetch"],
    )
    cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, None)

    tools_idx = cmd.index("--allowedTools")
    tools_str = cmd[tools_idx + 1]
    tools_list = tools_str.split(",")
    assert tools_list.count("WebFetch") == 1
    assert "WebSearch" in tools_list


def test_build_docker_command_oauth_env(tmp_path: Path) -> None:
    root, agent_dir, pkg = _make_agent(tmp_path)
    cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, None)

    assert "-e" in cmd
    e_idx = cmd.index("-e")
    assert cmd[e_idx + 1] == "CLAUDE_CODE_OAUTH_TOKEN"


def test_build_docker_command_tty_detection(tmp_path: Path) -> None:
    root, agent_dir, pkg = _make_agent(tmp_path)

    with patch("sys.stdout") as mock_stdout:
        mock_stdout.isatty.return_value = True
        cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, None)
        assert "-it" in cmd

    with patch("sys.stdout") as mock_stdout:
        mock_stdout.isatty.return_value = False
        cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, None)
        assert "-i" in cmd
        assert "-it" not in cmd


def test_build_docker_command_nonstandard_model(tmp_path: Path) -> None:
    root, agent_dir, pkg = _make_agent(tmp_path)
    cmd = build_docker_command(root, agent_dir, pkg, "custom-model", None, None)
    assert "--model" not in cmd


def test_build_docker_command_no_role_md(tmp_path: Path) -> None:
    root = tmp_path / "project"
    root.mkdir()
    agent_dir = tmp_path / "agent"
    agent_dir.mkdir()
    pkg = AgentPackage(name="no-role")

    cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, None)
    assert "--system-prompt" not in cmd


def test_build_docker_command_no_memory(tmp_path: Path) -> None:
    root = tmp_path / "project"
    root.mkdir()
    agent_dir = tmp_path / "agent"
    agent_dir.mkdir()
    pkg = AgentPackage(name="no-mem")

    cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, None)
    assert "--append-system-prompt" not in cmd


def test_build_docker_command_custom_image(tmp_path: Path) -> None:
    root, agent_dir, pkg = _make_agent(tmp_path)
    cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, None, image="my-image:v1")
    assert "my-image:v1" in cmd


def test_build_docker_command_memory_skips_header_only(tmp_path: Path) -> None:
    root, agent_dir, pkg = _make_agent(tmp_path)
    cmd = build_docker_command(root, agent_dir, pkg, "sonnet", None, None)

    if "--append-system-prompt" in cmd:
        idx = cmd.index("--append-system-prompt")
        memory_content = cmd[idx + 1]
        assert "CONTEXT" in memory_content
        assert "EMPTY" not in memory_content


# ── DockerCheckResult / DockerStatus ─────────────────────────


def test_docker_status_values() -> None:
    assert DockerStatus.READY == "ready"
    assert DockerStatus.NOT_INSTALLED == "not_installed"
    assert DockerStatus.NOT_RUNNING == "not_running"


def test_docker_check_result_dataclass() -> None:
    r = DockerCheckResult(status=DockerStatus.READY, message="ok")
    assert r.status == DockerStatus.READY
    assert r.message == "ok"
