"""Docker container runtime queries and network management."""

import logging
import subprocess
import time
from typing import Any

logger = logging.getLogger(__name__)


def get_container_id(instance_name: str) -> str | None:
    """Get the container ID for a compose service.

    Uses exact name matching to avoid substring collisions with tunnel
    containers (e.g. tunnel-code-server-... matching code-server-...).
    Falls back to case-insensitive matching since Docker DNS is case-
    insensitive but docker inspect is case-sensitive.

    Args:
        instance_name: The expected container name.

    Returns:
        Container ID or None if not found.
    """
    expected = instance_name.lower()

    # Fast path: exact match via docker inspect
    result = subprocess.run(
        ["docker", "inspect", "-f", "{{.Id}}", expected],
        capture_output=True,
        text=True,
    )
    if result.returncode == 0 and result.stdout.strip():
        return result.stdout.strip()

    # Fallback: list all containers and do case-insensitive exact match
    ps_result = subprocess.run(
        ["docker", "ps", "-a", "--format", "{{.Names}}\t{{.ID}}"],
        capture_output=True,
        text=True,
    )
    if ps_result.returncode == 0:
        for line in ps_result.stdout.strip().splitlines():
            parts = line.split("\t")
            if len(parts) == 2:
                name, cid = parts
                if name.lower() == expected:
                    return cid
    return None


def get_container_name(instance_name: str) -> str | None:
    """Get the full container name for a compose service.

    Uses exact name matching via docker inspect to avoid substring collisions.

    Args:
        instance_name: The exact container name (case-insensitive for Docker).

    Returns:
        Container name or None if not found.
    """
    result = subprocess.run(
        ["docker", "inspect", "-f", "{{.Name}}", instance_name.lower()],
        capture_output=True,
        text=True,
    )
    if result.returncode == 0 and result.stdout.strip():
        return result.stdout.strip().lstrip("/")
    return None


def get_backend_network_name() -> str:
    """Auto-detect the actual Docker network name for the backend network.

    Docker Compose prefixes network names with the project directory name
    (e.g. 'headquarter_backend' instead of 'backend'). We inspect the API
    container itself to find the real network name it's connected to.

    Returns:
        The actual Docker network name, or 'backend' as fallback.
    """
    # Try to find the API container by its known name
    api_container = "hq-api"
    result = subprocess.run(
        [
            "docker",
            "inspect",
            "-f",
            "{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}",
            api_container,
        ],
        capture_output=True,
        text=True,
    )
    if result.returncode == 0 and result.stdout.strip():
        networks = result.stdout.strip().split()
        for net in networks:
            if "backend" in net.lower():
                return net
        # API container is on some network — return the first one
        return networks[0]
    return "backend"


def connect_container_to_network(
    container_name: str, network_name: str | None = None
) -> bool:
    """Connect a Docker container to an existing network.

    Args:
        container_name: Name or ID of the container
        network_name: Name of the Docker network. If None, auto-detects
            from the API container's own network membership.

    Returns:
        True if successful, False otherwise
    """
    if network_name is None:
        network_name = get_backend_network_name()
    result = subprocess.run(
        ["docker", "network", "connect", network_name, container_name],
        capture_output=True,
        text=True,
    )
    return result.returncode == 0


def get_container_ip_on_network(
    container_id: str, network_name: str | None = None
) -> str | None:
    """Get a container's IP address on a specific Docker network.

    Args:
        container_id: Docker container ID or name.
        network_name: Network name. If None, auto-detects from the API container.

    Returns:
        IP address string, or None if the container is not on that network.
    """
    if network_name is None:
        network_name = get_backend_network_name()
    result = subprocess.run(
        [
            "docker",
            "inspect",
            "-f",
            f"{{{{.NetworkSettings.Networks.{network_name}.IPAddress}}}}",
            container_id,
        ],
        capture_output=True,
        text=True,
    )
    if result.returncode == 0:
        ip = result.stdout.strip()
        if ip and ip != "<no value>":
            return ip
    return None


def is_container_on_network(container_id: str, network_name: str | None = None) -> bool:
    """Check whether a container is already attached to a Docker network.

    Args:
        container_id: Docker container ID or name.
        network_name: Network name. If None, auto-detects from the API container.

    Returns:
        True if the container is on the network.
    """
    if network_name is None:
        network_name = get_backend_network_name()
    result = subprocess.run(
        [
            "docker",
            "inspect",
            "-f",
            f"{{{{.NetworkSettings.Networks.{network_name}}}}}",
            container_id,
        ],
        capture_output=True,
        text=True,
    )
    return result.returncode == 0 and "<no value>" not in result.stdout


def get_container_status(container_id: str) -> dict[str, Any]:
    """Get the status of a Docker container.

    Args:
        container_id: Docker container ID

    Returns:
        Dict with 'status' (running, exited, restarting, not_found),
        'exit_code' (int or None), and 'health' (health status or None)
    """
    result = subprocess.run(
        [
            "docker",
            "inspect",
            "-f",
            "{{.State.Status}}|{{.State.ExitCode}}|{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}",
            container_id,
        ],
        capture_output=True,
        text=True,
    )

    if result.returncode != 0:
        return {"status": "not_found", "exit_code": None, "health": None}

    parts = result.stdout.strip().split("|")
    status = parts[0] if parts else "unknown"
    exit_code = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else None
    health = parts[2] if len(parts) > 2 and parts[2] != "none" else None

    return {"status": status, "exit_code": exit_code, "health": health}


def wait_for_container_running(
    container_id: str, timeout: int = 30, interval: float = 2.0
) -> dict[str, Any]:
    """Wait for a container to reach the running state.

    Polls docker inspect until the container status is "running" or timeout.

    Args:
        container_id: Docker container ID
        timeout: Maximum seconds to wait
        interval: Seconds between polls

    Returns:
        Dict with 'success' (bool), 'status' (str), 'exit_code' (int or None),
        and 'waited_seconds' (float)
    """

    start_time = time.time()

    while time.time() - start_time < timeout:
        info = get_container_status(container_id)

        if info["status"] == "running":
            return {
                "success": True,
                "status": "running",
                "exit_code": None,
                "waited_seconds": time.time() - start_time,
            }

        if info["status"] == "exited":
            return {
                "success": False,
                "status": "exited",
                "exit_code": info["exit_code"],
                "waited_seconds": time.time() - start_time,
            }

        if info["status"] == "not_found":
            return {
                "success": False,
                "status": "not_found",
                "exit_code": None,
                "waited_seconds": time.time() - start_time,
            }

        time.sleep(interval)

    # Timeout reached
    info = get_container_status(container_id)
    return {
        "success": False,
        "status": info["status"],
        "exit_code": info["exit_code"],
        "waited_seconds": time.time() - start_time,
    }


def get_container_logs(container_id: str, tail: int = 100) -> str:
    """Get the logs of a Docker container.

    Args:
        container_id: Docker container ID
        tail: Number of lines to return

    Returns:
        Container logs
    """
    result = subprocess.run(
        ["docker", "logs", "--tail", str(tail), container_id],
        capture_output=True,
        text=True,
    )

    if result.returncode == 0:
        return result.stdout
    return f"Failed to get logs: {result.stderr}"


def find_free_port(start: int = 10000, end: int = 20000) -> int:
    """Find a free TCP port in the given range.

    Args:
        start: Start of port range
        end: End of port range

    Returns:
        Free port number
    """
    import socket

    for port in range(start, end):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            if s.connect_ex(("localhost", port)) != 0:
                return port

    raise RuntimeError(f"No free port found in range {start}-{end}")
