"""Podman container management tools."""

from __future__ import annotations

import shlex
from contextlib import contextmanager
from typing import TYPE_CHECKING, Annotated

from fastmcp.tools import tool
from podman import PodmanClient
from podman.errors import APIError, NotFound
from pydantic import Field

from mcp_vm_blackbox._helpers import _resolve, _ssh_run
from mcp_vm_blackbox.models import (
    CommandResult,
    PodmanContainer,
    PodmanLogsResult,
    PodmanPsError,
    PodmanRestartResult,
    PodmanServiceStatusResult,
)

if TYPE_CHECKING:
    from collections.abc import Generator

    from mcp_vm_blackbox.targets import TargetConfig

# ---------------------------------------------------------------------------
# Podman client helper
# ---------------------------------------------------------------------------

_DEFAULT_REMOTE_SOCKET = "/run/podman/podman.sock"
_DEFAULT_LOCAL_BASE_URL = "unix:///run/podman/podman.sock"
_DEMUX_TUPLE_LEN = 2  # exec_run(demux=True) returns (stdout_bytes, stderr_bytes)


def _podman_base_url(cfg: TargetConfig) -> str:
    """Build the Podman base URL for PodmanClient from target config.

    Args:
        cfg: Resolved target configuration.

    Returns:
        base_url string for PodmanClient (SSH or unix).
    """
    socket_path = cfg.podman_socket_path or _DEFAULT_REMOTE_SOCKET
    if not cfg.is_remote:
        if cfg.podman_socket_path and not cfg.podman_socket_path.startswith("unix://"):
            return f"unix://{cfg.podman_socket_path}"
        return cfg.podman_socket_path or _DEFAULT_LOCAL_BASE_URL

    # Build SSH URI: ssh://[user@]host[:port]/path?secure=True
    host_part = cfg.ssh_host
    if cfg.ssh_user and "@" not in host_part:
        host_part = f"{cfg.ssh_user}@{host_part}"
    port_suffix = f":{cfg.ssh_port}" if cfg.ssh_port else ""
    path = socket_path if socket_path.startswith("/") else f"/{socket_path}"
    base = f"ssh://{host_part}{port_suffix}{path}"
    if "?" not in base:
        base = f"{base}?secure=True"
    return base


@contextmanager
def _podman_client(cfg: TargetConfig) -> Generator[PodmanClient, None, None]:
    """Create a PodmanClient for the given target config.

    Args:
        cfg: Resolved target configuration.

    Yields:
        Connected PodmanClient (closed on exit).
    """
    base_url = _podman_base_url(cfg)
    kwargs: dict[str, str | int | bool] = {"base_url": base_url}
    if cfg.is_remote and cfg.ssh_identity_file:
        kwargs["identity"] = cfg.ssh_identity_file
    client = PodmanClient(**kwargs)
    try:
        yield client
    finally:
        client.close()


# ---------------------------------------------------------------------------
# Tools
# ---------------------------------------------------------------------------


@tool
def podman_ps(
    target: Annotated[str, Field(description='Named target or raw SSH string. Defaults to "db-host".')] = "db-host",
) -> list[PodmanContainer] | list[PodmanPsError]:
    """List all Podman containers on the target (running and stopped).

    Args:
        target: Named target or raw SSH string.  Defaults to ``"db-host"``.

    Returns:
        List of PodmanContainer on success, or list of PodmanPsError on failure.
    """
    cfg = _resolve(target)
    try:
        with _podman_client(cfg) as client:
            containers_raw = client.containers.list(all=True)
    except (APIError, NotFound, OSError, ConnectionError, ValueError, RuntimeError) as exc:
        return [PodmanPsError(error=f"podman ps failed: {exc!s}")]

    containers: list[PodmanContainer] = []
    for c in containers_raw:
        attrs = getattr(c, "attrs", {}) or {}
        cid = c.id or c.short_id or attrs.get("Id", "")
        names = [c.name] if c.name else []
        if not names and "Names" in attrs:
            raw_names = attrs["Names"]
            names = [n.lstrip("/") for n in raw_names] if isinstance(raw_names, list) else []
        elif not names and "Name" in attrs:
            names = [str(attrs["Name"]).lstrip("/")]

        ports: list[str] = []
        ports_attr = c.ports or {}
        if not ports_attr and "NetworkSettings" in attrs:
            ports_attr = attrs.get("NetworkSettings", {}).get("Ports", {})
        for k, v in (ports_attr or {}).items():
            if isinstance(v, list) and v:
                for entry in v:
                    host_port = entry.get("HostPort", "") if isinstance(entry, dict) else str(entry)
                    ports.append(f"{k}:{host_port}" if host_port else str(k))
            elif v:
                ports.append(f"{k}:{v}")
            else:
                ports.append(str(k))

        image_str = attrs.get("ImageName") or attrs.get("Config", {}).get("Image", "") or ""
        if not image_str and c.image:
            image_str = str(c.image)

        status_val = str(c.status) if c.status else attrs.get("State", {}).get("Status", "unknown")

        containers.append(PodmanContainer(id=cid, names=names, status=status_val, ports=ports, image=str(image_str)))
    return containers


@tool
def podman_exec(
    container: Annotated[str, Field(description="Container name or ID (use podman_ps to discover).")],
    command: Annotated[str, Field(description="Shell command string to execute inside the container.")],
    target: Annotated[str, Field(description='Named target or raw SSH string. Defaults to "db-host".')] = "db-host",
) -> CommandResult:
    """Run a command inside a Podman container on the target.

    Args:
        container: Container name or ID (use :func:`podman_ps` to discover).
        command: Shell command string to execute inside the container.
        target: Named target or raw SSH string.  Defaults to ``"db-host"``.

    Returns:
        CommandResult with stdout, stderr, exit_code.
    """
    cfg = _resolve(target)
    cmd_parts = shlex.split(command)
    try:
        with _podman_client(cfg) as client:
            c = client.containers.get(container)
            exit_code, output = c.exec_run(cmd_parts, demux=True)
    except (APIError, NotFound, OSError, ConnectionError, ValueError, RuntimeError) as exc:
        return CommandResult(stdout="", stderr=str(exc), exit_code=-1, error=str(exc))

    def _to_str(b: bytes) -> str:
        return b.decode("utf-8", errors="replace") if b else ""

    if isinstance(output, tuple) and len(output) >= _DEMUX_TUPLE_LEN:
        stdout = _to_str(output[0] if isinstance(output[0], bytes) else b"")
        stderr = _to_str(output[1] if isinstance(output[1], bytes) else b"")
    elif isinstance(output, bytes):
        stdout = _to_str(output)
        stderr = ""
    else:
        stdout = ""
        stderr = ""
    return CommandResult(stdout=stdout, stderr=stderr, exit_code=exit_code if exit_code is not None else -1)


@tool
def podman_logs(
    container: Annotated[str, Field(description="Container name or ID (use podman_ps to discover).")],
    tail: Annotated[int, Field(description="Number of log lines to return.", ge=1)] = 50,
    target: Annotated[str, Field(description='Named target or raw SSH string. Defaults to "db-host".')] = "db-host",
) -> PodmanLogsResult:
    """Fetch the last N log lines from a Podman container on the target.

    Args:
        container: Container name or ID (use :func:`podman_ps` to discover).
        tail: Number of log lines to return.  Defaults to ``50``.
        target: Named target or raw SSH string.  Defaults to ``"db-host"``.

    Returns:
        PodmanLogsResult with lines, container, tail.  On failure, error is set.
    """
    cfg = _resolve(target)
    try:
        with _podman_client(cfg) as client:
            c = client.containers.get(container)
            raw = c.logs(tail=tail, stdout=True, stderr=True)
    except (APIError, NotFound, OSError, ConnectionError, ValueError, RuntimeError) as exc:
        return PodmanLogsResult(lines=[], container=container, tail=tail, error=f"podman logs failed: {exc!s}")

    text = raw.decode("utf-8", errors="replace") if isinstance(raw, bytes) else str(raw)
    lines = text.splitlines()
    return PodmanLogsResult(lines=lines, container=container, tail=tail)


@tool
def podman_restart(
    container: Annotated[str, Field(description="Container name or ID (use podman_ps to discover).")],
    timeout: Annotated[int, Field(description="Seconds to wait for container to stop before killing.", ge=1)] = 10,
    target: Annotated[str, Field(description='Named target or raw SSH string. Defaults to "db-host".')] = "db-host",
) -> PodmanRestartResult:
    """Restart a Podman container on the target.

    Args:
        container: Container name or ID (use :func:`podman_ps` to discover).
        timeout: Seconds to wait for container to stop before killing.
        target: Named target or raw SSH string.  Defaults to ``"db-host"``.

    Returns:
        PodmanRestartResult with success/error.
    """
    cfg = _resolve(target)
    try:
        with _podman_client(cfg) as client:
            c = client.containers.get(container)
            c.restart(timeout=timeout)
        return PodmanRestartResult(container=container, success=True)
    except (APIError, NotFound, OSError, ConnectionError, ValueError, RuntimeError) as exc:
        return PodmanRestartResult(container=container, success=False, error=str(exc))


@tool
async def podman_service_status(
    service: Annotated[str, Field(description='Systemd service name, e.g. "postgresql.service".')],
    target: Annotated[str, Field(description='Named target or raw SSH string. Defaults to "db-host".')] = "db-host",
) -> PodmanServiceStatusResult:
    """Check the systemd service status for a Podman-managed service on the target.

    Args:
        service: Systemd service name (e.g. ``"postgresql.service"``).
        target: Named target or raw SSH string.  Defaults to ``"db-host"``.

    Returns:
        PodmanServiceStatusResult with active, status_output, service.
    """
    cfg = _resolve(target)
    result = await _ssh_run(cfg, ["sudo", "systemctl", "status", service, "--no-pager", "-l"])
    status_output = result.stdout + result.stderr
    active = "Active: active" in status_output
    return PodmanServiceStatusResult(active=active, status_output=status_output, service=service)
