"""Local image build/inspect helpers for the dynamic installer."""

from __future__ import annotations

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

import typer
import yaml

from armorer.catalog import resolve_playbook
from armorer.knowledge.hints import load_agent_hints
from armorer.docker_runner import docker_image_inspect, docker_image_inspect_format


def compose_source_revision(*, cwd: Path) -> Optional[str]:
    """Best-effort git revision for the compose workspace."""
    try:
        res = subprocess.run(
            ["git", "rev-parse", "HEAD"],
            cwd=str(cwd),
            capture_output=True,
            text=True,
        )
        if res.returncode == 0:
            rev = (res.stdout or "").strip()
            return rev or None
    except Exception:
        return None
    return None


def docker_image_source_revision(image: str) -> Optional[str]:
    """Read the `armorer.openclaw.source_rev` label from an image, if present."""
    try:
        result = docker_image_inspect_format(image, "{{json .Config.Labels}}")
    except Exception:
        return None

    if not result.ok:
        return None

    try:
        labels = json.loads((result.stdout or "").strip() or "{}")
    except Exception:
        return None
    value = (labels.get("armorer.openclaw.source_rev") or "").strip()
    return value or None


def ensure_local_compose_images_impl(
    installer: Any,
    runtime_env: Optional[dict[str, str]] | None = None,
    *,
    console: Any,
    load_dotenv_kv,
    resolve_compose_var_default,
) -> None:
    """Extracted logic from `DynamicInstaller._ensure_local_compose_images`."""
    hints = load_agent_hints(installer.app_name)
    hinted_local_images = set(hints.get("local_images", [])) if isinstance(hints, dict) else set()

    compose_path = installer._docker_compose_cwd / "docker-compose.yml"
    if not compose_path.exists():
        compose_path = installer._docker_compose_cwd / "docker-compose.yaml"
    if not compose_path.exists():
        return

    try:
        data = yaml.safe_load(compose_path.read_text()) or {}
    except Exception:
        return

    services = data.get("services", {})
    if not isinstance(services, dict):
        return

    source_rev = compose_source_revision(cwd=installer._docker_compose_cwd)

    # Compose reads .env from the working directory by default.
    compose_env = load_dotenv_kv(installer._docker_compose_cwd / ".env")
    if runtime_env:
        compose_env.update(runtime_env)

    for svc_name, svc in services.items():
        if not isinstance(svc, dict):
            continue
        image = svc.get("image")
        if not image or not isinstance(image, str):
            continue

        if "${" in image:
            resolved = resolve_compose_var_default(image, compose_env)
            if resolved:
                image = resolved
            else:
                continue
            if not image.endswith(":local") and image not in hinted_local_images:
                continue

        # Prefer the installer hook so unit tests can control existence checks
        # without requiring a real Docker daemon.
        img_exists = bool(installer._docker_image_exists(image))
        if img_exists and source_rev:
            label_rev = docker_image_source_revision(image)
            if label_rev == source_rev:
                continue
        elif img_exists:
            continue

        if svc.get("build"):
            console.print(
                f"  [dim]Building missing local image via compose: {svc_name} -> {image}[/dim]"
            )
            installer._run_with_live_logs(
                installer._compose_cmd() + ["build", svc_name],
                cwd=installer._docker_compose_cwd,
                label=f"Building image for {svc_name}",
                check=False,
            )
            if not installer._docker_image_exists(image):
                raise typer.Exit(f"Required local image is still missing after build: {image}")
            continue

        dockerfile = installer._docker_compose_cwd / "Dockerfile"
        if dockerfile.exists():
            console.print(f"  [dim]Building missing or stale local image: {image}[/dim]")
            installer._docker_build_local_image(
                image,
                installer._docker_compose_cwd,
                label=f"Building image {image}",
                source_rev=source_rev,
            )
            if not installer._docker_image_exists(image):
                raise typer.Exit(f"Required local image is still missing after build: {image}")

