"""Integration tests proving fleet status and destroy guard through JSON-RPC.

Exercises the complete lifecycle:
  registry populate → job start → step → fleet status (R005) →
  vagrant_destroy blocked (R006) → job complete → destroy succeeds.

Deferred imports
----------------
``fastmcp`` is intentionally **not** imported at module level.  Importing it
at collection time (before conftest patches are in place) causes
``ModuleNotFoundError`` for ``rich.logging`` in pytest-xdist workers.  All
fastmcp symbols are accessed via local imports inside test bodies — matching
the pattern established in ``test_job_lifecycle.py``.
"""

from __future__ import annotations

import json
from typing import TYPE_CHECKING
from unittest.mock import AsyncMock

if TYPE_CHECKING:
    import types
    from pathlib import Path

    import pytest

import mcp_vm_blackbox.fleet_tools as fleet_tools_mod
import mcp_vm_blackbox.job_store as job_store_mod
import mcp_vm_blackbox.job_tools as job_tools_mod
import mcp_vm_blackbox.vagrant_tools as vagrant_tools_mod
from mcp_vm_blackbox.job_store import JobStore
from mcp_vm_blackbox.models import VmListEntry
from mcp_vm_blackbox.targets import TargetConfig
from mcp_vm_blackbox.vm_registry import VMRegistry


class TestFleetLifecycle:
    """Integration test exercising R005 + R006 end-to-end through fastmcp.Client."""

    async def test_fleet_lifecycle(
        self, server_module: types.ModuleType, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Full lifecycle: populate → start → step → fleet status → destroy blocked → complete → destroy ok.

        Assertions:
        - ``vm_fleet_status`` returns FleetEntry with active job data
        - ``vagrant_destroy`` returns is_error=True when an active job exists
        - ``vagrant_destroy`` succeeds after the job is completed
        - Job file persisted on disk
        """
        from fastmcp import Client

        # --- Isolated stores ---
        store = JobStore(tmp_path)
        registry = VMRegistry(tmp_path)

        # Monkeypatch job_store on all modules that reference it
        monkeypatch.setattr(job_tools_mod, "default_job_store", store)
        monkeypatch.setattr(fleet_tools_mod, "default_job_store", store)
        monkeypatch.setattr(job_store_mod, "default_job_store", store)

        # Monkeypatch registry on fleet_tools
        monkeypatch.setattr(fleet_tools_mod, "default_registry", registry)

        # Monkeypatch agent ID
        monkeypatch.setattr(job_tools_mod, "_AGENT_ID", "test-agent")

        # Mock vagrant internals so vagrant_destroy doesn't need real Vagrant
        local_cfg = TargetConfig(ssh_host="")
        monkeypatch.setattr(vagrant_tools_mod, "_resolve", lambda _target: local_cfg)

        mock_run = AsyncMock(return_value=("", "", 0, 0.1))
        monkeypatch.setattr(vagrant_tools_mod, "_vagrant_run", mock_run)

        mock_run_detached = AsyncMock()
        monkeypatch.setattr(vagrant_tools_mod, "_vagrant_run_detached", mock_run_detached)

        # --- Populate registry with a test VM ---
        vm_name = "fleet-test-vm"
        await registry.populate([VmListEntry(name=vm_name, uuid="uuid-fleet-1", running=True)])

        async with Client(server_module.mcp) as client:
            # ── 1. Start a job ──────────────────────────────────
            start_result = await client.call_tool(
                "vm_job_start", {"vm_name": vm_name, "scenario_name": "install-os", "step": "boot VM"}
            )
            start_data = json.loads(start_result.content[0].text)
            job_id = start_data["job_id"]
            assert start_data["status"] == "active"

            # ── 2. Append a step ────────────────────────────────
            step_result = await client.call_tool(
                "vm_job_step",
                {"job_id": job_id, "vm_name": vm_name, "action": "run installer wizard", "outcome": "page 1 complete"},
            )
            step_data = json.loads(step_result.content[0].text)
            assert step_data["status"] == "step_appended"

            # ── 3. Call vm_fleet_status (R005) ──────────────────
            fleet_result = await client.call_tool("vm_fleet_status", {})
            fleet_data = json.loads(fleet_result.content[0].text)

            assert len(fleet_data) == 1, f"Expected 1 VM, got {len(fleet_data)}"
            entry = fleet_data[0]
            assert entry["vm_name"] == vm_name
            assert entry["uuid"] == "uuid-fleet-1"
            assert entry["running"] is True
            assert len(entry["jobs"]) == 1

            job_summary = entry["jobs"][0]
            assert job_summary["job_id"] == job_id
            assert job_summary["status"] == "active"
            assert job_summary["scenario_name"] == "install-os"
            assert job_summary["last_agent_id"] == "test-agent"
            assert job_summary["last_step"] == "run installer wizard"

            # ── 4. vagrant_destroy should be BLOCKED (R006) ─────
            #    Use raise_on_error=False so the ToolError surfaces as
            #    is_error on the CallToolResult instead of propagating.
            destroy_result = await client.call_tool("vagrant_destroy", {"vm": vm_name}, raise_on_error=False)
            assert destroy_result.is_error is True, f"Expected isError=True from destroy guard, got: {destroy_result}"
            error_text = destroy_result.content[0].text
            assert f"Cannot destroy VM '{vm_name}'" in error_text
            assert job_id in error_text
            assert "install-os" in error_text

            # ── 5. Complete the job ─────────────────────────────
            complete_result = await client.call_tool("vm_job_complete", {"job_id": job_id, "vm_name": vm_name})
            complete_data = json.loads(complete_result.content[0].text)
            assert complete_data["status"] == "completed"

            # ── 6. vagrant_destroy should now SUCCEED ───────────
            destroy_ok_result = await client.call_tool("vagrant_destroy", {"vm": vm_name})
            assert destroy_ok_result.is_error is not True, (
                f"Expected destroy to succeed after job completion, got error: {destroy_ok_result.content[0].text}"
            )

        # ── Post-session: verify file persistence ──────────────
        job_path = tmp_path / "jobs" / vm_name / f"{job_id}.json"
        assert job_path.exists(), f"Job file missing at {job_path}"

        raw = json.loads(job_path.read_text(encoding="utf-8"))
        assert raw["status"] == "completed"
        assert raw["job_id"] == job_id


class TestFleetStatusDiscoverable:
    """Verify ``vm_fleet_status`` is discoverable via ``list_tools``."""

    async def test_fleet_status_discoverable(self, server_module: types.ModuleType) -> None:
        """``vm_fleet_status`` must appear in the tool list."""
        from fastmcp import Client

        async with Client(server_module.mcp) as client:
            tools = await client.list_tools()

        tool_names = {t.name for t in tools}
        assert "vm_fleet_status" in tool_names, (
            f"vm_fleet_status not found in list_tools. Available tools: {sorted(tool_names)}"
        )
