#!/usr/bin/env node
// Restore the node/lane artifact catalog from the GitOps branch before CI planning.

import { execFileSync, spawnSync } from "node:child_process";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname } from "node:path";

import {
  artifactCatalogAuthorityPath,
  artifactCatalogServiceCoverage,
  createBootstrapArtifactCatalogAuthority,
  createHydratedArtifactCatalogAuthority
} from "../src/artifact-catalog-authority.mjs";

const startedAt = Date.now();

function env(name) {
  const value = process.env[name];
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}

function csvEnv(name) {
  return (env(name) ?? "").split(",").map((value) => value.trim()).filter(Boolean);
}

function emit(payload) {
  process.stderr.write(JSON.stringify({
    event: "artifact-catalog-restore",
    source: "scripts/ci/restore-artifact-catalog.mjs",
    at: new Date().toISOString(),
    durationMs: Date.now() - startedAt,
    catalogPath,
    gitopsBranch,
    ...payload
  }) + "\n");
}

function git(args, options = {}) {
  return execFileSync("git", args, {
    cwd: process.cwd(),
    encoding: "utf8",
    stdio: options.capture === false ? ["ignore", "ignore", "pipe"] : ["ignore", "pipe", "pipe"],
    timeout: options.timeoutMs ?? 45_000
  });
}

function gitProbe(args, options = {}) {
  const result = spawnSync("git", args, {
    cwd: process.cwd(),
    encoding: "utf8",
    stdio: ["ignore", "pipe", "pipe"],
    timeout: options.timeoutMs ?? 45_000
  });
  if (result.error) throw result.error;
  return {
    status: result.status ?? 1,
    stdout: result.stdout ?? "",
    stderr: result.stderr ?? ""
  };
}

const catalogPath = env("HWLAB_CATALOG_PATH");
const gitopsBranch = env("HWLAB_GITOPS_BRANCH");
const remote = env("HWLAB_GIT_READ_URL") ?? env("HWLAB_GIT_URL");
const selectedServices = csvEnv("HWLAB_SERVICES");

if (!catalogPath || !gitopsBranch || !remote || selectedServices.length === 0) {
  emit({
    status: "failed",
    reason: "required-input-missing",
    hasCatalogPath: Boolean(catalogPath),
    hasGitopsBranch: Boolean(gitopsBranch),
    hasRemote: Boolean(remote),
    selectedServiceCount: selectedServices.length
  });
  process.exit(2);
}

try {
  const selectedServiceCoverageInput = uniqueSorted(selectedServices);
  if (selectedServiceCoverageInput.length !== selectedServices.length) {
    throw new Error("selected services contain duplicate service ids");
  }
  const branchProbe = gitProbe(["ls-remote", "--exit-code", "--heads", remote, `refs/heads/${gitopsBranch}`], { timeoutMs: 45_000 });
  if (branchProbe.status === 2) {
    restoreBootstrapOrMissing({ reason: "gitops-branch-missing", selectedServices: selectedServiceCoverageInput });
    process.exit(0);
  }
  if (branchProbe.status !== 0) {
    throw new Error(`gitops branch probe failed: ${boundedError(branchProbe.stderr)}`);
  }
  git(["fetch", "--depth=1", remote, `refs/heads/${gitopsBranch}`], { capture: false, timeoutMs: 60_000 });
  const gitopsCommitId = git(["rev-parse", "FETCH_HEAD"], { timeoutMs: 15_000 }).trim();
  const catalogProbe = gitProbe(["ls-tree", "-z", "--full-tree", "--name-only", "FETCH_HEAD", "--", catalogPath], { timeoutMs: 15_000 });
  if (catalogProbe.status !== 0) {
    throw new Error(`gitops catalog tree probe failed: ${boundedError(catalogProbe.stderr)}`);
  }
  if (catalogProbe.stdout.length === 0) {
    restoreBootstrapOrMissing({ reason: "gitops-catalog-missing", selectedServices: selectedServiceCoverageInput });
    process.exit(0);
  }
  const content = git(["show", `FETCH_HEAD:${catalogPath}`], { timeoutMs: 30_000 });
  const parsed = JSON.parse(content);
  if (!Array.isArray(parsed?.services) || parsed.services.length === 0) {
    throw new Error("gitops catalog has no services");
  }
  const coverage = artifactCatalogServiceCoverage(parsed, selectedServiceCoverageInput);
  const authorityPath = artifactCatalogAuthorityPath(catalogPath);
  const authority = createHydratedArtifactCatalogAuthority({
    catalog: parsed,
    catalogPath,
    gitopsBranch,
    gitopsCommitId,
    selectedServices: selectedServiceCoverageInput
  });
  mkdirSync(dirname(catalogPath), { recursive: true });
  writeFileSync(catalogPath, JSON.stringify(parsed, null, 2) + "\n");
  writeFileSync(authorityPath, JSON.stringify(authority, null, 2) + "\n");
  emit({
    status: "hydrated",
    reason: "gitops-catalog",
    authority: authority.authority,
    authorityPath,
    gitopsCommitId,
    catalogSourceCommitId: authority.catalogSourceCommitId,
    catalogSha256: authority.catalogSha256,
    serviceCount: parsed.services.length,
    selectedServiceCount: selectedServices.length,
    coverage,
    bytes: Buffer.byteLength(content)
  });
} catch (error) {
  emit({
    status: "failed",
    reason: "gitops-catalog-unavailable",
    error: error instanceof Error ? error.message.slice(0, 500) : String(error).slice(0, 500)
  });
  process.exit(1);
}

function restoreBootstrapOrMissing({ reason, selectedServices }) {
  const authorityPath = artifactCatalogAuthorityPath(catalogPath);
  if (!existsSync(catalogPath)) {
    if (existsSync(authorityPath)) throw new Error(`artifact catalog authority exists without catalog ${catalogPath}`);
    emit({
      status: "missing",
      reason,
      authority: "none",
      selectedServiceCount: selectedServices.length
    });
    return;
  }
  const catalog = JSON.parse(readFileSync(catalogPath, "utf8"));
  const sourceCommitId = git(["rev-parse", "HEAD"], { timeoutMs: 15_000 }).trim();
  validateBootstrapCatalog({ catalog, sourceCommitId, selectedServices });
  const authority = createBootstrapArtifactCatalogAuthority({
    catalog,
    catalogPath,
    sourceCommitId,
    reason,
    selectedServices
  });
  writeFileSync(authorityPath, JSON.stringify(authority, null, 2) + "\n");
  emit({
    status: "bootstrap",
    reason,
    authority: authority.authority,
    authorityPath,
    sourceCommitId,
    serviceCount: authority.serviceCount,
    catalogSha256: authority.catalogSha256,
    coverage: authority.coverage
  });
}

function uniqueSorted(values) {
  return [...new Set(values)].sort();
}

function validateBootstrapCatalog({ catalog, sourceCommitId, selectedServices }) {
  if (catalog?.catalogVersion !== "v1" || catalog?.kind !== "hwlab-artifact-catalog" || catalog?.artifactState !== "contract-skeleton" || catalog?.environment !== "v02" || catalog?.profile !== "v02") {
    throw new Error(`existing source catalog is not an allowed v02 contract skeleton for ${catalogPath}`);
  }
  if (catalog?.publish?.sourceCommitId !== sourceCommitId) {
    throw new Error(`v02 contract skeleton source commit does not match HEAD for ${catalogPath}`);
  }
  const coverage = artifactCatalogServiceCoverage(catalog, selectedServices);
  if (coverage.status !== "exact") {
    throw new Error(`v02 contract skeleton service coverage is ${coverage.status} for ${catalogPath}`);
  }
  const unsafe = catalog.services.filter((service) => (
    service?.sourceCommitId !== sourceCommitId
    || service?.buildBackend !== "contract-skeleton"
    || service?.digest !== null
    || (service?.environmentDigest !== undefined && service?.environmentDigest !== null)
  ));
  if (unsafe.length > 0) throw new Error(`v02 contract skeleton contains reusable artifacts for ${catalogPath}`);
}

function boundedError(value) {
  const result = String(value ?? "").replace(/\s+/gu, " ").trim();
  return result ? result.slice(0, 300) : "exit-without-stderr";
}
