"""Snapshot management tools — snapshot_create, snapshot_list.

Tools register on the FastMCP server instance imported from server.py.
All tool handlers are wrapped with @structured_tool_handler (D-09).
"""

from __future__ import annotations

import logging

from vmcluster_mcp.cluster.libvirt_client import LibvirtClient
from vmcluster_mcp.models import ToolResult
from vmcluster_mcp.server import config, mcp, structured_tool_handler
from vmcluster_mcp.snapshot.manager import SnapshotManager
from vmcluster_mcp.topology.parser import load_topology

logger = logging.getLogger("vmcluster_mcp.snapshot")

# Single shared client — connection is opened lazily on first use
_client = LibvirtClient(config.libvirt_uri)
_manager = SnapshotManager(_client)


@mcp.tool()
@structured_tool_handler
async def snapshot_create(
    cluster_name: str,
    snapshot_name: str,
    include_memory: bool = False,
) -> ToolResult:
    """Create an external disk-only snapshot of all nodes in a cluster.

    Creates a named snapshot for every node concurrently using asyncio.gather.
    Snapshot overlay files are stored under
    {overlay_dir}/{cluster_name}/snapshots/{snapshot_name}/.

    If include_memory=True is passed, a warning is logged and disk-only mode
    is used (memory snapshots are not supported in v1).

    Idempotent guard: returns error if a snapshot with the given name already
    exists in libvirt.

    Args:
        cluster_name: Name of the cluster topology (maps to topology YAML file).
        snapshot_name: Unique name for this snapshot.
        include_memory: Accepted but ignored in v1 — always disk-only.

    Returns:
        ToolResult[SnapshotInfo] with per-node disk paths and sizes on success.
    """
    if include_memory:
        logger.warning(
            "include_memory=True requested but only disk-only snapshots are "
            "supported in v1; proceeding disk-only"
        )

    topo = load_topology(cluster_name, config.topology_dir)
    return await _manager.create_snapshot(
        cluster_name=topo.cluster_name,
        snapshot_name=snapshot_name,
        nodes=topo.nodes,
        overlay_dir=topo.overlay_dir,
    )


@mcp.tool()
@structured_tool_handler
async def snapshot_list(cluster_name: str) -> ToolResult:
    """List all snapshots for a cluster with creation timestamps and per-node sizes.

    Queries libvirt for all snapshots on each domain, parses creation time
    from snapshot XML, and reports per-node disk file sizes.

    Args:
        cluster_name: Name of the cluster topology.

    Returns:
        ToolResult[list[SnapshotInfo]] — empty list if no snapshots exist.
    """
    topo = load_topology(cluster_name, config.topology_dir)
    return await _manager.list_snapshots(
        cluster_name=topo.cluster_name,
        nodes=topo.nodes,
        overlay_dir=topo.overlay_dir,
    )


@mcp.tool()
@structured_tool_handler
async def snapshot_revert(
    cluster_name: str,
    snapshot_name: str,
    restart_after: bool = True,
    wait_for_ssh: bool = True,
    ssh_timeout_s: int = 120,
) -> ToolResult:
    """Revert all nodes in a cluster to a named snapshot.

    Destroy-all-before-revert: all domains are force-stopped before any revert
    begins (libvirt has no atomic multi-domain revert primitive).

    With restart_after=True (default): domains are started after revert and
    (if wait_for_ssh=True) SSH reachability is confirmed before returning.
    Any node failure or SSH timeout returns success=False; no silent partial
    reverts occur (D-07).

    Args:
        cluster_name: Name of the cluster topology.
        snapshot_name: Name of the snapshot to revert to.
        restart_after: Boot all nodes after revert (default True).
        wait_for_ssh: Block until all nodes are SSH-reachable (requires restart_after=True).
        ssh_timeout_s: Per-node SSH wait timeout in seconds.

    Returns:
        ToolResult[ClusterStatus] with per-node domain state and SSH reachability.
    """
    topo = load_topology(cluster_name, config.topology_dir)
    return await _manager.revert_snapshot(
        cluster_name=topo.cluster_name,
        snapshot_name=snapshot_name,
        nodes=topo.nodes,
        overlay_dir=topo.overlay_dir,
        topo=topo,
        restart_after=restart_after,
        wait_for_ssh=wait_for_ssh,
        ssh_timeout_s=ssh_timeout_s,
    )


@mcp.tool()
@structured_tool_handler
async def snapshot_delete(
    cluster_name: str,
    snapshot_name: str,
) -> ToolResult:
    """Delete a named snapshot from all nodes in a cluster.

    Best-effort per-node: failure on one node is reported but does not abort
    other nodes (D-08). Per-node success/failure status is returned in result.

    Active-backing guard (D-05): refuses to delete a snapshot if its overlay
    file is the currently-active disk source for any node's domain.

    Args:
        cluster_name: Name of the cluster topology.
        snapshot_name: Name of the snapshot to delete.

    Returns:
        ToolResult[SnapshotDeleteResult] with per-node deletion status.
        success=True only if all nodes successfully deleted the snapshot.
    """
    topo = load_topology(cluster_name, config.topology_dir)
    return await _manager.delete_snapshot(
        cluster_name=topo.cluster_name,
        snapshot_name=snapshot_name,
        nodes=topo.nodes,
    )
