from __future__ import annotations

import json
import subprocess
from pathlib import Path
from typing import Any

from daisy_sdk.constants import DEFAULT_CONTAINER_NAME
from daisy_sdk.image import build_command, collect_project_config_paths
from daisy_sdk.utils import DEFAULT_PROJECT_CONFIG_PATH, run_command


class DockerError(RuntimeError):
    """Raised when docker commands fail for non-benign reasons."""


def _run_docker(args: list[str]) -> subprocess.CompletedProcess[str]:
    try:
        return run_command(["docker", *args], capture_output=True)
    except FileNotFoundError as exc:  # docker not installed/available
        raise DockerError("Docker is not installed or not available on PATH") from exc
    except subprocess.CalledProcessError as exc:
        stderr = (exc.stderr or "").strip()
        stdout = (exc.stdout or "").strip()
        payload = "\n".join(part for part in [stdout, stderr] if part)
        raise DockerError(payload or "Docker command failed") from exc


def start_container(
    image: str,
    *,
    port: str = "8080",
    project_config_path: Path = DEFAULT_PROJECT_CONFIG_PATH,
    platform: str | None = "linux/amd64",
    container_name: str = DEFAULT_CONTAINER_NAME,
    env: dict[str, str] | None = None,
    pull: bool = False,
    print_command: bool = False,
) -> list[str]:
    """Start the Daisy container detached with a stable name."""
    cmd = build_command(
        image,
        port,
        project_config_path,
        run=False,
        platform=platform,
        container_name=container_name,
        detach=True,
        env=env,
        pull=pull,
        print_command=print_command,
    )
    try:
        subprocess.run(cmd, check=True)
    except FileNotFoundError as exc:
        raise DockerError("Docker is not installed or not available on PATH") from exc
    except subprocess.CalledProcessError as exc:
        stderr = (exc.stderr or "").strip()
        stdout = (exc.stdout or "").strip()
        payload = "\n".join(part for part in [stdout, stderr] if part)
        raise DockerError(payload or "Failed to start container") from exc
    return cmd


def stop_container(container_name: str = DEFAULT_CONTAINER_NAME) -> None:
    """Stop and remove the named container; ignore if it doesn't exist."""
    try:
        subprocess.run(["docker", "rm", "-f", container_name], check=False, capture_output=True)
    except FileNotFoundError as exc:
        raise DockerError("Docker is not installed or not available on PATH") from exc


def container_running(container_name: str = DEFAULT_CONTAINER_NAME) -> bool:
    try:
        result = _run_docker(
            [
                "ps",
                "--filter",
                f"name={container_name}",
                "--format",
                "{{.Names}}",
            ]
        )
    except DockerError:
        return False
    names = [line.strip() for line in (result.stdout or "").splitlines()]
    return container_name in names


def describe_container(container_name: str = DEFAULT_CONTAINER_NAME) -> dict[str, Any]:
    """Return minimal container info if running; otherwise empty dict."""
    try:
        result = _run_docker(
            [
                "ps",
                "--filter",
                f"name={container_name}",
                "--format",
                "{{json .}}",
            ]
        )
    except DockerError:
        return {}
    lines = [line.strip() for line in (result.stdout or "").splitlines() if line.strip()]
    if not lines:
        return {}
    try:
        return json.loads(lines[0])
    except json.JSONDecodeError:
        return {}


def project_mounts(project_config_path: Path = DEFAULT_PROJECT_CONFIG_PATH) -> list[str]:
    """Return unique project + BI paths from the config file, for visibility/logging."""
    try:
        import tomllib

        cfg = {}
        if project_config_path.exists():
            with project_config_path.open("rb") as handle:
                cfg = tomllib.load(handle)
    except Exception:
        return []
    return collect_project_config_paths(cfg)
