import type { UniDeskConfig } from "../config";
import { capture, compactCapture, parseJsonOutput, shQuote } from "../platform-infra-public-service";
import { startJob } from "../jobs";

import { readSub2ApiConfig } from "./config";
import { imageRef, resolveTarget, targetDependencyImages } from "./manifest";
import type { ApplyOptions } from "./options";
import { boolField } from "./utils";

function imagePrepullScript(images: string[], confirm: boolean): string {
  const imageList = images.join("\n");
  const mode = confirm ? "confirm" : "dry-run";
  return `
set -u
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
mode=${shQuote(mode)}
images_file="$tmp/images.txt"
cat > "$images_file" <<'EOF_IMAGES'
${imageList}
EOF_IMAGES
docker_bin=""
if docker ps >/dev/null 2>&1; then
  docker_bin="docker"
elif sudo docker ps >/dev/null 2>&1; then
  docker_bin="sudo docker"
fi
crictl_bin=""
if command -v crictl >/dev/null 2>&1; then
  crictl_bin="crictl"
elif sudo crictl images >/dev/null 2>&1; then
  crictl_bin="sudo crictl"
fi
runtime="none"
if [ -n "$docker_bin" ]; then
  runtime="docker"
elif [ -n "$crictl_bin" ]; then
  runtime="crictl"
fi
jsonl="$tmp/images.jsonl"
: > "$jsonl"
ok=true
while IFS= read -r image; do
  [ -n "$image" ] || continue
  present_before=false
  present_after=false
  pull_rc=0
  inspect_id=""
  inspect_digest=""
  safe_name="$(printf '%s' "$image" | tr '/:@' '___')"
  pull_stdout="$tmp/$safe_name.pull.out"
  pull_stderr="$tmp/$safe_name.pull.err"
  : > "$pull_stdout"
  : > "$pull_stderr"
  if [ "$runtime" = "docker" ]; then
    if $docker_bin image inspect "$image" >/dev/null 2>&1; then present_before=true; fi
    if [ "$mode" = "confirm" ]; then
      $docker_bin pull "$image" >"$pull_stdout" 2>"$pull_stderr"
      pull_rc=$?
    fi
    if $docker_bin image inspect "$image" >/dev/null 2>&1; then
      present_after=true
      inspect_id="$($docker_bin image inspect "$image" --format '{{.Id}}' 2>/dev/null || true)"
      inspect_digest="$($docker_bin image inspect "$image" --format '{{join .RepoDigests ","}}' 2>/dev/null || true)"
    fi
  elif [ "$runtime" = "crictl" ]; then
    if $crictl_bin inspecti "$image" >/dev/null 2>&1; then present_before=true; fi
    if [ "$mode" = "confirm" ]; then
      $crictl_bin pull "$image" >"$pull_stdout" 2>"$pull_stderr"
      pull_rc=$?
    fi
    if $crictl_bin inspecti "$image" >/dev/null 2>&1; then
      present_after=true
      inspect_id="$($crictl_bin inspecti "$image" 2>/dev/null | python3 -c 'import json,sys; data=json.load(sys.stdin); print(data.get("status",{}).get("id") or data.get("id") or "")' 2>/dev/null || true)"
    fi
  else
    pull_rc=1
    printf '%s\\n' 'neither docker nor crictl is available for image prepull' >"$pull_stderr"
  fi
  if [ "$mode" = "confirm" ]; then
    if [ "$pull_rc" -ne 0 ] || [ "$present_after" != true ]; then ok=false; fi
  else
    if [ "$runtime" = "none" ]; then ok=false; fi
  fi
  python3 - "$jsonl" "$image" "$runtime" "$present_before" "$present_after" "$pull_rc" "$inspect_id" "$inspect_digest" "$pull_stdout" "$pull_stderr" <<'PY'
import json
import sys

path, image, runtime, before, after, pull_rc, inspect_id, inspect_digest, pull_stdout, pull_stderr = sys.argv[1:]

def text(file_path: str, limit: int = 4000) -> str:
    try:
        return open(file_path, encoding="utf-8", errors="replace").read()[-limit:]
    except FileNotFoundError:
        return ""

record = {
    "image": image,
    "runtime": runtime,
    "presentBefore": before == "true",
    "presentAfter": after == "true",
    "pull": {
        "exitCode": int(pull_rc),
        "stdout": text(pull_stdout),
        "stderr": text(pull_stderr),
    },
    "imageId": inspect_id or None,
    "repoDigests": [item for item in inspect_digest.split(",") if item],
}
with open(path, "a", encoding="utf-8") as handle:
    handle.write(json.dumps(record, ensure_ascii=False) + "\\n")
PY
done < "$images_file"
python3 - "$jsonl" "$ok" "$mode" "$runtime" <<'PY'
import json
import sys

jsonl, ok, mode, runtime = sys.argv[1:]
images = []
try:
    with open(jsonl, encoding="utf-8") as handle:
        for line in handle:
            if line.strip():
                images.append(json.loads(line))
except FileNotFoundError:
    pass
payload = {
    "ok": ok == "true",
    "mode": mode,
    "runtime": runtime,
    "images": images,
    "valuesPrinted": False,
}
print(json.dumps(payload, ensure_ascii=False, indent=2))
sys.exit(0 if payload["ok"] else 1)
PY
`;
}

export async function imagePrepull(config: UniDeskConfig, options: ApplyOptions): Promise<Record<string, unknown>> {
  const sub2api = readSub2ApiConfig();
  const target = resolveTarget(sub2api, options.targetId);
  const dependencyImages = targetDependencyImages(sub2api, target);
  const images = Array.from(new Set([imageRef(sub2api, target), dependencyImages.redis]));
  if (options.confirm && !options.wait) {
    const job = startJob(
      `platform_infra_sub2api_image_prepull_${target.id.toLowerCase()}`,
      ["bun", "scripts/cli.ts", "platform-infra", "sub2api", "image-prepull", "--target", target.id, "--confirm", "--wait"],
      `Pre-pull ${target.id} Sub2API runtime images from YAML-controlled image refs`,
    );
    return {
      ok: true,
      action: "platform-infra-sub2api-image-prepull",
      mode: "async-job",
      target: { id: target.id, route: target.route, namespace: target.namespace },
      images,
      job,
      statusCommand: `bun scripts/cli.ts job status ${job.id} --tail-bytes 12000`,
      next: {
        status: `bun scripts/cli.ts job status ${job.id} --tail-bytes 12000`,
        verify: `bun scripts/cli.ts platform-infra sub2api image-prepull --target ${target.id}`,
        apply: `bun scripts/cli.ts platform-infra sub2api apply --target ${target.id} --confirm`,
      },
    };
  }
  const result = await capture(
    config,
    target.route,
    ["sh"],
    imagePrepullScript(images, options.confirm),
    options.confirm
      ? { runtimeTimeoutMs: sub2api.defaults.rollout.imagePrepullTimeoutSeconds * 1000 }
      : {},
  );
  const parsed = parseJsonOutput(result.stdout);
  return {
    ok: result.exitCode === 0 && boolField(parsed, "ok", false),
    action: "platform-infra-sub2api-image-prepull",
    mode: options.confirm ? "confirmed" : "dry-run",
    target: { id: target.id, route: target.route, namespace: target.namespace },
    images,
    timeoutSeconds: options.confirm ? sub2api.defaults.rollout.imagePrepullTimeoutSeconds : null,
    remote: parsed ?? compactCapture(result, { full: true }),
    next: {
      apply: `bun scripts/cli.ts platform-infra sub2api apply --target ${target.id} --confirm`,
      status: `bun scripts/cli.ts platform-infra sub2api status --target ${target.id}`,
    },
  };
}
