"""Tests for podman_tools MCP tools.

Tests: podman_ps, podman_exec, podman_logs, podman_restart, podman_service_status.
How: Mock _podman_client (for podman-py tools) and _ssh_run (for systemctl) so tests
     run offline without a real host.
Why: CI environments lack Podman; tools must use mock PodmanClient / containers.
"""

from __future__ import annotations

from contextlib import contextmanager
from typing import TYPE_CHECKING
from unittest.mock import AsyncMock, MagicMock

import pytest

from mcp_vm_blackbox.models import PodmanContainer, PodmanPsError
from mcp_vm_blackbox.targets import TargetConfig

if TYPE_CHECKING:
    from pytest_mock import MockerFixture


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def mock_resolve(mocker: MockerFixture) -> MagicMock:
    """Patch _resolve to return a local TargetConfig for all targets."""
    cfg = TargetConfig(ssh_host="")
    return mocker.patch("mcp_vm_blackbox.podman_tools._resolve", return_value=cfg)


@pytest.fixture
def mock_ssh_run(mocker: MockerFixture) -> AsyncMock:
    """Patch _ssh_run for controlled subprocess output (podman_service_status, async)."""
    proc = MagicMock()
    proc.stdout = ""
    proc.stderr = ""
    proc.returncode = 0
    return mocker.patch("mcp_vm_blackbox.podman_tools._ssh_run", new_callable=AsyncMock, return_value=proc)


def _make_container(
    *,
    container_id: str = "abc123",
    name: str = "postgres",
    names: list[str] | None = None,
    status: str = "running",
    image: str = "postgres:16",
    ports: dict | None = None,
    attrs: dict | None = None,
) -> MagicMock:
    """Create a mock Container object for podman-py."""
    c = MagicMock()
    c.id = container_id
    c.short_id = container_id[:12] if len(container_id) > 12 else container_id
    c.name = name
    c.status = status
    c.ports = ports or {}
    c.image = MagicMock()
    c.image.tags = [image] if image else []
    c.attrs = attrs or {
        "Id": container_id,
        "Names": names or [f"/{name}"],
        "Name": name,
        "State": {"Status": status},
        "ImageName": image,
        "Config": {"Image": image},
        "NetworkSettings": {"Ports": ports or {}},
    }
    return c


# ---------------------------------------------------------------------------
# podman_ps
# ---------------------------------------------------------------------------


class TestPodmanPs:
    """Tests for podman_ps tool."""

    def test_podman_ps_returns_containers(self, mock_resolve: MagicMock, mocker: MockerFixture) -> None:
        """podman_ps maps Container objects into PodmanContainer models."""
        c1 = _make_container(
            container_id="abc123",
            name="postgres",
            status="running",
            image="postgres:16",
            ports={"5432/tcp": [{"HostPort": "5432"}]},
        )
        c2 = _make_container(container_id="def456", name="redis", status="exited", image="redis:7", ports={})
        containers = [c1, c2]

        @contextmanager
        def fake_client(cfg: object):
            client = MagicMock()
            client.containers.list.return_value = containers
            yield client

        mocker.patch("mcp_vm_blackbox.podman_tools._podman_client", fake_client)

        from mcp_vm_blackbox.podman_tools import podman_ps

        result = podman_ps(target="db-host")

        assert isinstance(result, list)
        assert len(result) == 2
        assert isinstance(result[0], PodmanContainer)
        assert result[0].id == "abc123"
        assert result[0].names == ["postgres"]
        assert result[0].status == "running"
        assert "5432" in str(result[0].ports)
        assert result[0].image == "postgres:16"
        assert isinstance(result[1], PodmanContainer)
        assert result[1].id == "def456"
        assert result[1].names == ["redis"]
        assert result[1].status == "exited"

    def test_podman_ps_non_zero_exit_returns_podman_ps_error(
        self, mock_resolve: MagicMock, mocker: MockerFixture
    ) -> None:
        """podman_ps returns PodmanPsError list when PodmanClient raises."""

        @contextmanager
        def fake_client(cfg: object):
            raise OSError("permission denied")
            yield  # unreachable, for contextmanager

        mocker.patch("mcp_vm_blackbox.podman_tools._podman_client", fake_client)

        from mcp_vm_blackbox.podman_tools import podman_ps

        result = podman_ps(target="db-host")

        assert len(result) == 1
        assert isinstance(result[0], PodmanPsError)
        assert "podman ps failed" in result[0].error
        assert "permission denied" in result[0].error

    def test_podman_ps_empty_list_returns_empty_list(self, mock_resolve: MagicMock, mocker: MockerFixture) -> None:
        """podman_ps returns empty list when no containers."""

        @contextmanager
        def fake_client(cfg: object):
            client = MagicMock()
            client.containers.list.return_value = []
            yield client

        mocker.patch("mcp_vm_blackbox.podman_tools._podman_client", fake_client)

        from mcp_vm_blackbox.podman_tools import podman_ps

        result = podman_ps(target="db-host")

        assert result == []


# ---------------------------------------------------------------------------
# podman_exec
# ---------------------------------------------------------------------------


class TestPodmanExec:
    """Tests for podman_exec tool."""

    def test_podman_exec_returns_command_result(self, mock_resolve: MagicMock, mocker: MockerFixture) -> None:
        """podman_exec returns CommandResult with stdout, stderr, exit_code."""
        container = MagicMock()
        container.exec_run.return_value = (0, (b"Hello World", b""))

        @contextmanager
        def fake_client(cfg: object):
            client = MagicMock()
            client.containers.get.return_value = container
            yield client

        mocker.patch("mcp_vm_blackbox.podman_tools._podman_client", fake_client)

        from mcp_vm_blackbox.podman_tools import podman_exec

        result = podman_exec(container="postgres", command="echo Hello World", target="db-host")

        assert result.stdout == "Hello World"
        assert result.stderr == ""
        assert result.exit_code == 0

    def test_podman_exec_passes_shell_command(self, mock_resolve: MagicMock, mocker: MockerFixture) -> None:
        """podman_exec splits command with shlex and passes to exec_run."""
        container = MagicMock()
        container.exec_run.return_value = (0, (b"", b""))

        @contextmanager
        def fake_client(cfg: object):
            client = MagicMock()
            client.containers.get.return_value = container
            yield client

        mocker.patch("mcp_vm_blackbox.podman_tools._podman_client", fake_client)

        from mcp_vm_blackbox.podman_tools import podman_exec

        podman_exec(container="postgres", command='echo "quoted arg"', target="db-host")

        container.exec_run.assert_called_once()
        call_args = container.exec_run.call_args[0]
        assert "quoted arg" in call_args[0]


# ---------------------------------------------------------------------------
# podman_logs
# ---------------------------------------------------------------------------


class TestPodmanLogs:
    """Tests for podman_logs tool."""

    def test_podman_logs_returns_lines(self, mock_resolve: MagicMock, mocker: MockerFixture) -> None:
        """podman_logs parses container.logs output into lines."""
        container = MagicMock()
        container.logs.return_value = b"line1\nline2\nline3"

        @contextmanager
        def fake_client(cfg: object):
            client = MagicMock()
            client.containers.get.return_value = container
            yield client

        mocker.patch("mcp_vm_blackbox.podman_tools._podman_client", fake_client)

        from mcp_vm_blackbox.podman_tools import podman_logs

        result = podman_logs(container="postgres", tail=50, target="db-host")

        assert result.lines == ["line1", "line2", "line3"]
        assert result.container == "postgres"
        assert result.tail == 50
        assert result.error is None

    def test_podman_logs_failure_sets_error(self, mock_resolve: MagicMock, mocker: MockerFixture) -> None:
        """podman_logs returns error when container.logs raises."""

        @contextmanager
        def fake_client(cfg: object):
            raise RuntimeError("no such container")
            yield  # unreachable, for contextmanager

        mocker.patch("mcp_vm_blackbox.podman_tools._podman_client", fake_client)

        from mcp_vm_blackbox.podman_tools import podman_logs

        result = podman_logs(container="missing", tail=10, target="db-host")

        assert result.lines == []
        assert result.error == "podman logs failed: no such container"


# ---------------------------------------------------------------------------
# podman_restart
# ---------------------------------------------------------------------------


class TestPodmanRestart:
    """Tests for podman_restart tool."""

    def test_podman_restart_success(self, mock_resolve: MagicMock, mocker: MockerFixture) -> None:
        """podman_restart returns success when restart succeeds."""
        container = MagicMock()

        @contextmanager
        def fake_client(cfg: object):
            client = MagicMock()
            client.containers.get.return_value = container
            yield client

        mocker.patch("mcp_vm_blackbox.podman_tools._podman_client", fake_client)

        from mcp_vm_blackbox.podman_tools import podman_restart

        result = podman_restart(container="postgres", target="db-host")

        assert result.success is True
        assert result.container == "postgres"
        assert result.error is None
        container.restart.assert_called_once()

    def test_podman_restart_failure_sets_error(self, mock_resolve: MagicMock, mocker: MockerFixture) -> None:
        """podman_restart returns error when restart raises."""

        @contextmanager
        def fake_client(cfg: object):
            raise RuntimeError("container not found")
            yield  # unreachable, for contextmanager

        mocker.patch("mcp_vm_blackbox.podman_tools._podman_client", fake_client)

        from mcp_vm_blackbox.podman_tools import podman_restart

        result = podman_restart(container="missing", target="db-host")

        assert result.success is False
        assert result.error == "container not found"


# ---------------------------------------------------------------------------
# podman_service_status
# ---------------------------------------------------------------------------


class TestPodmanServiceStatus:
    """Tests for podman_service_status tool."""

    async def test_podman_service_status_active(self, mock_resolve: MagicMock, mock_ssh_run: AsyncMock) -> None:
        """podman_service_status detects active when Active: active in output."""
        mock_ssh_run.return_value.stdout = "Active: active (running)"
        mock_ssh_run.return_value.stderr = ""
        mock_ssh_run.return_value.returncode = 0

        from mcp_vm_blackbox.podman_tools import podman_service_status

        result = await podman_service_status(service="postgresql.service", target="db-host")

        assert result.active is True
        assert "Active: active" in result.status_output
        assert result.service == "postgresql.service"

    async def test_podman_service_status_inactive(self, mock_resolve: MagicMock, mock_ssh_run: AsyncMock) -> None:
        """podman_service_status sets active=False when not active."""
        mock_ssh_run.return_value.stdout = "Active: inactive (dead)"
        mock_ssh_run.return_value.stderr = ""
        mock_ssh_run.return_value.returncode = 3

        from mcp_vm_blackbox.podman_tools import podman_service_status

        result = await podman_service_status(service="postgresql.service", target="db-host")

        assert result.active is False
