"""Podman connection management.

Handles listing Podman connections, opening/closing the SSH port-forward
tunnel, and resolving the local socket URL for direct connections.

Run standalone to verify connectivity::

    # Test SSH tunnel (default — mirrors what `cutip run` uses):
    python -m cutip.backends.podman.connection

    # Test local socket (mirrors what `cutip run --local` uses):
    python -m cutip.backends.podman.connection --local
"""

from __future__ import annotations

import os
import shlex
import socket
import subprocess
import sys
import time
from dataclasses import dataclass
from pathlib import Path

from loguru import logger

from cutip.utils.exceptions import CutipError

# ── Tunnel result ─────────────────────────────────────────────────────────────


@dataclass(frozen=True)
class TunnelInfo:
    """Result of opening an SSH tunnel — holds the process and the allocated port."""

    process: subprocess.Popen
    port: int

    @property
    def url(self) -> str:
        return f"tcp://localhost:{self.port}"


def _find_free_port() -> int:
    """Ask the OS for an available TCP port on localhost."""
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind(("localhost", 0))
        return s.getsockname()[1]


# ── Connection enumeration ─────────────────────────────────────────────────────


def get_connections() -> list[dict[str, str]]:
    """Return all Podman connections as a list of dicts (one per connection).

    Runs ``podman system connection ls`` and parses the tabular output.

    Raises:
        CutipError: If Podman is not installed or the command fails.
    """
    try:
        result = subprocess.run(
            ["podman", "system", "connection", "ls"],
            capture_output=True,
            text=True,
            check=True,
        )
    except (subprocess.CalledProcessError, FileNotFoundError) as exc:
        raise CutipError("Could not list Podman connections. Is Podman installed?") from exc

    lines = result.stdout.strip().splitlines()
    if len(lines) < 2:
        raise CutipError("No Podman connections found. Run 'podman machine start' first.")

    headers = lines[0].split()
    connections: list[dict[str, str]] = []
    for line in lines[1:]:
        parts = line.split(maxsplit=len(headers) - 1)
        if len(parts) < len(headers):
            continue
        connections.append(dict(zip(headers, parts)))
    return connections


def get_default_connection(connections: list[dict[str, str]] | None = None) -> dict[str, str]:
    """Return the default Podman connection dict.

    Args:
        connections: Pre-fetched list from :func:`get_connections`.
                     If *None*, :func:`get_connections` is called automatically.

    Raises:
        CutipError: If no default connection is found.
    """
    if connections is None:
        connections = get_connections()

    for conn in connections:
        if conn.get("Default", "").lower() == "true":
            return conn

    raise CutipError(
        "No default Podman connection found. "
        "Run 'podman machine start' and ensure a connection is marked as default."
    )


# ── SSH tunnel (used by PodmanBackend.connect()) ───────────────────────────────


def open_ssh_tunnel(connection: dict[str, str]) -> TunnelInfo:
    """Forward a dynamic localhost port → the Podman Unix socket inside the VM.

    Uses SSH local port forwarding (``-L``) with an OS-assigned free port,
    so multiple CUTIP workspaces can run simultaneously without collisions.

    Requires OpenSSH ≥ 6.7 (ships with macOS 10.12+ and modern Linux distros).

    Args:
        connection: A connection dict as returned by :func:`get_default_connection`.

    Returns:
        A :class:`TunnelInfo` with the running SSH process and allocated port.
        Keep alive for the session lifetime; close with :func:`close_ssh_tunnel`.

    Raises:
        CutipError: If the tunnel exits immediately (bad key, wrong host, etc.).
    """
    uri: str = connection.get("URI", "")
    identity: str = connection.get("Identity", "")

    # Parse ssh://user@host:port/path/to/podman.sock
    without_scheme = uri.replace("ssh://", "")
    if "/" in without_scheme:
        user_host_port, socket_tail = without_scheme.split("/", 1)
        socket_path = "/" + socket_tail  # restore leading slash
    else:
        user_host_port, socket_path = without_scheme, ""

    user, host_port = (
        user_host_port.split("@", 1) if "@" in user_host_port else ("root", user_host_port)
    )
    host, port = host_port.rsplit(":", 1) if ":" in host_port else (host_port, "22")

    local_port = _find_free_port()

    ssh_cmd = [
        "ssh",
        "-N",  # port-forward only, no shell
        "-L",
        f"{local_port}:{socket_path}",
        "-i",
        identity,
        "-p",
        port,
        f"{user}@{host}",
        "-o",
        "StrictHostKeyChecking=no",
        "-o",
        "ExitOnForwardFailure=yes",  # fail fast on bind error
    ]

    logger.debug(f"Opening SSH tunnel: {shlex.join(ssh_cmd)}")
    process = subprocess.Popen(ssh_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    time.sleep(1)

    if process.poll() is not None:
        stderr = process.stderr.read().decode(errors="replace").strip()
        raise CutipError(
            "SSH tunnel exited prematurely.\n"
            f"  URI:     {uri}\n"
            f"  Socket:  {socket_path}\n"
            f"  Command: {shlex.join(ssh_cmd)}\n"
            f"  stderr:  {stderr or '(none)'}\n"
            "Ensure 'podman machine start' has been run and the machine is healthy."
        )

    tunnel = TunnelInfo(process=process, port=local_port)
    logger.info(f"SSH tunnel established: {tunnel.url} -> {socket_path}")
    return tunnel


def close_ssh_tunnel(tunnel: TunnelInfo | subprocess.Popen | None, timeout: int = 5) -> None:
    """Terminate the SSH tunnel process gracefully.

    Args:
        tunnel: A :class:`TunnelInfo` or raw :class:`subprocess.Popen`.
        timeout: Seconds to wait for graceful termination before force-killing.
    """
    if tunnel is None:
        return
    process = tunnel.process if isinstance(tunnel, TunnelInfo) else tunnel
    if process.poll() is not None:
        return
    logger.debug("Closing SSH tunnel...")
    process.terminate()
    try:
        process.wait(timeout=timeout)
    except subprocess.TimeoutExpired:
        logger.warning("SSH tunnel did not exit cleanly — force-killing.")
        process.kill()
    logger.debug("SSH tunnel closed.")


# ── Local socket (used by PodmanBackend.connect_local()) ──────────────────────


def local_socket_url() -> str:
    """Resolve the local Podman socket URL for the current platform.

    Priority order:
    1. ``CONTAINER_HOST`` env var (standard Podman convention)
    2. macOS: socket path reported by ``podman machine inspect``
    3. Linux: ``$XDG_RUNTIME_DIR/podman/podman.sock``
    4. Windows: raises :class:`CutipError` (named pipes unsupported by podman-py)

    Raises:
        CutipError: On Windows or if the macOS socket cannot be located.
    """
    env = os.environ.get("CONTAINER_HOST")
    if env:
        return env

    if sys.platform == "win32":
        raise CutipError(
            "connect_local() (--local) is not supported on Windows because "
            "podman-py does not support the Windows named-pipe protocol.\n"
            "Use SSH-tunnel mode instead (no --local flag) after running "
            "'podman machine start'."
        )

    if sys.platform == "darwin":
        try:
            result = subprocess.run(
                [
                    "podman",
                    "machine",
                    "inspect",
                    "--format",
                    "{{.ConnectionInfo.PodmanSocket.Path}}",
                ],
                capture_output=True,
                text=True,
                check=True,
            )
            sock = result.stdout.strip()
            if sock:
                return f"unix://{sock}"
        except Exception:
            pass
        # Fallback: well-known socket locations for each VM provider
        home = Path.home()
        for candidate in [
            home / ".local/share/containers/podman/machine/applehv/podman.sock",
            home / ".local/share/containers/podman/machine/qemu/podman.sock",
        ]:
            if candidate.exists():
                return f"unix://{candidate}"
        raise CutipError(
            "Could not locate the macOS Podman machine socket. "
            "Set CONTAINER_HOST or run 'podman machine start'."
        )

    # Linux — rootless user socket
    uid = os.getuid()
    xdg = os.environ.get("XDG_RUNTIME_DIR", f"/run/user/{uid}")
    return f"unix://{xdg}/podman/podman.sock"


# ── Standalone test ────────────────────────────────────────────────────────────

if __name__ == "__main__":
    """Verify Podman connectivity without running a full workflow.

    Usage::

        python -m cutip.backends.podman.connection           # SSH tunnel
        python -m cutip.backends.podman.connection --local   # local socket
    """
    from cutip.utils.logging import setup_logging

    setup_logging(level="DEBUG")

    use_local = "--local" in sys.argv

    if use_local:
        # ── Local socket mode ──────────────────────────────────────────────
        logger.info("Mode: local socket")
        url = local_socket_url()
        logger.info(f"Socket URL: {url}")

        try:
            from podman import PodmanClient
        except ImportError:
            logger.error("podman-py not installed. Run: uv add podman")
            sys.exit(1)

        client = PodmanClient(base_url=url)
        ok = client.ping()
        if not ok:
            logger.error(f"Ping failed at {url}. Is 'podman machine start' running?")
            sys.exit(1)

        logger.info("Ping OK")
        images = client.images.list()
        logger.info(f"Images on daemon: {len(images)}")
        for img in images[:5]:
            logger.info(f"  {img.tags}")

    else:
        # ── SSH tunnel mode ────────────────────────────────────────────────
        logger.info("Mode: SSH tunnel")
        connections = get_connections()
        conn = get_default_connection(connections)
        logger.info(f"Default connection: {conn['Name']}")
        logger.info(f"  URI:      {conn['URI']}")
        logger.info(f"  Identity: {conn['Identity']}")

        try:
            from podman import PodmanClient
        except ImportError:
            logger.error("podman-py not installed. Run: uv add podman")
            sys.exit(1)

        tunnel = open_ssh_tunnel(conn)
        logger.info(f"Tunnel port: {tunnel.port}")
        try:
            client = PodmanClient(base_url=tunnel.url)
            ok = client.ping()
            if not ok:
                logger.error("Ping failed — tunnel is up but Podman is not responding.")
                sys.exit(1)

            logger.info("Ping OK")
            images = client.images.list()
            logger.info(f"Images on daemon: {len(images)}")
            for img in images[:5]:
                logger.info(f"  {img.tags}")
        finally:
            close_ssh_tunnel(tunnel)
