"""Docker Engine installation and configuration helpers."""
from __future__ import annotations

from k4s.core.executor import Executor
from k4s.core.products import Step
from k4s.recipes.common.linux import (
    apt_install,
    ensure_apt_updated,
)
from k4s.recipes.common.run import check, q, run
from k4s.ui.ui import Ui


def install_docker(
    ui: Ui,
    ex: Executor,
    *,
    data_root: str | None = None,
) -> None:
    """Install Docker Engine from Docker's official Ubuntu repository.

    Optionally configures a custom data-root directory.
    Adding a user to the docker group is handled separately via
    :func:`k4s.recipes.common.linux.ensure_user_in_group`.
    """
    ui.log("Installing Docker Engine from Docker's Ubuntu repository.")
    ensure_apt_updated(ex)
    apt_install(ex, ["ca-certificates", "curl", "gnupg", "lsb-release"])
    check(ex, "sudo -n install -m 0755 -d /etc/apt/keyrings")
    check(
        ex,
        "curl -fsSL https://download.docker.com/linux/ubuntu/gpg "
        "| gpg --dearmor | sudo -n tee /etc/apt/keyrings/docker.gpg > /dev/null",
    )
    check(ex, "sudo -n chmod a+r /etc/apt/keyrings/docker.gpg")
    check(
        ex,
        'OS_CODENAME=$(lsb_release -cs) && '
        'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] '
        'https://download.docker.com/linux/ubuntu $OS_CODENAME stable" | '
        "sudo -n tee /etc/apt/sources.list.d/docker.list > /dev/null",
    )
    ensure_apt_updated(ex)
    apt_install(
        ex,
        ["docker-ce", "docker-ce-cli", "containerd.io", "docker-buildx-plugin", "docker-compose-plugin"],
    )

    if data_root:
        ui.log(f"Configuring Docker data-root to {data_root}.")
        check(ex, "sudo -n systemctl stop docker.service || true")
        check(ex, "sudo -n systemctl stop docker.socket || true")
        check(ex, f"sudo -n mkdir -p {q(data_root)}")
        _merge_daemon_json_data_root(ex, data_root)
        check(ex, "sudo -n systemctl start docker")


def _merge_daemon_json(ex: Executor, updates: dict) -> None:
    """Merge key/value pairs into /etc/docker/daemon.json (idempotent).

    Preserves any existing configuration keys. Creates daemon.json if missing.
    Fails if the existing file is invalid JSON.
    """
    check(ex, "sudo -n install -m 0755 -d /etc/docker")
    updates_repr = repr(updates)
    cmd = (
        "sudo -n python3 - <<'PY'\n"
        "import json, os, sys\n"
        "path = '/etc/docker/daemon.json'\n"
        f"updates = {updates_repr}\n"
        "data = {}\n"
        "if os.path.exists(path):\n"
        "    with open(path, 'r', encoding='utf-8') as f:\n"
        "        raw = f.read().strip()\n"
        "    if raw:\n"
        "        try:\n"
        "            data = json.loads(raw)\n"
        "        except Exception as e:\n"
        "            print('Invalid JSON in /etc/docker/daemon.json. Fix it or remove it, then retry.', file=sys.stderr)\n"
        "            raise\n"
        "if not isinstance(data, dict):\n"
        "    raise SystemExit('daemon.json must be a JSON object')\n"
        "for k, v in updates.items():\n"
        "    if isinstance(v, list) and k in data and isinstance(data[k], list):\n"
        "        merged = list(data[k])\n"
        "        for item in v:\n"
        "            if item not in merged:\n"
        "                merged.append(item)\n"
        "        data[k] = merged\n"
        "    else:\n"
        "        data[k] = v\n"
        "tmp = path + '.k4s.tmp'\n"
        "with open(tmp, 'w', encoding='utf-8') as f:\n"
        "    json.dump(data, f, indent=2, sort_keys=True)\n"
        "    f.write('\\n')\n"
        "os.replace(tmp, path)\n"
        "os.chmod(path, 0o644)\n"
        "PY"
    )
    check(ex, cmd)


def _merge_daemon_json_data_root(ex: Executor, data_root: str) -> None:
    """Merge Docker daemon.json and set the data-root key."""
    _merge_daemon_json(ex, {"data-root": data_root})


def add_insecure_registry(ex: Executor, registry: str) -> None:
    """Add *registry* to Docker's insecure-registries list (idempotent).

    Restarts Docker if daemon.json was changed.
    """
    _, out, _ = run(ex, "sudo -n cat /etc/docker/daemon.json 2>/dev/null || echo '{}'", silent=True)
    import json as _json
    try:
        current = _json.loads(out or "{}")
    except Exception:
        current = {}
    existing = current.get("insecure-registries", [])
    if registry in existing:
        return  # already present, no restart needed

    _merge_daemon_json(ex, {"insecure-registries": [registry]})
    check(ex, "sudo -n systemctl restart docker")


def build_docker_steps(
    ui: Ui,
    ex: Executor,
    *,
    data_root: str | None = None,
) -> list[Step]:
    """Return a list of steps that install and configure Docker.

    Adding a user to the docker group is intentionally excluded here.
    Use :func:`k4s.recipes.common.linux.ensure_user_in_group` in a
    dedicated step so callers have full control over when it runs.
    """

    def _run() -> None:
        install_docker(ui, ex, data_root=data_root)

    return [Step(title="Install and configure Docker", run=_run)]
