import { execFile } from "node:child_process";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";

import {
  DEV_CODE_AGENT_PROVIDER_CONTRACT,
  inspectCodeAgentProviderManifestRefs
} from "../../internal/cloud/code-agent-contract.ts";
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.ts";
import { tempReportPath } from "./report-paths.mjs";
import { readStructuredFile } from "./structured-config.mjs";

const execFileAsync = promisify(execFile);
const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");

const deployPath = "deploy/deploy.yaml";
const catalogPath = "deploy/artifact-catalog.dev.json";
const workloadsPath = "deploy/k8s/base/workloads.yaml";
const artifactReportPath = tempReportPath("dev-artifacts.json");
const mutableTags = new Set(["latest", "dev", "main", "master", "prod", "production"]);
const mirrorEnvNames = [
  "HWLAB_COMMIT_ID",
  "HWLAB_IMAGE",
  "HWLAB_IMAGE_TAG",
  "HWLAB_SKILLS_COMMIT_ID",
  "HWLAB_IMAGE_DIGEST"
];
const forbiddenDeployArtifactFields = new Set(["commitId", "image", "imageTag", "digest", "sourceCommitId", "repositoryDigest", "publishState"]);
const forbiddenDeployArtifactEnv = new Set(mirrorEnvNames.concat(["HWLAB_REVISION", "HWLAB_BUILD_CREATED_AT", "HWLAB_BUILD_SOURCE"]));
const commitMirrorEnvNames = new Set(["HWLAB_COMMIT_ID", "HWLAB_SKILLS_COMMIT_ID"]);
const imageMirrorEnvNames = new Set(["HWLAB_IMAGE"]);
const tagMirrorEnvNames = new Set(["HWLAB_IMAGE_TAG"]);
const digestMirrorEnvNames = new Set(["HWLAB_IMAGE_DIGEST"]);
const tagPattern = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/;
const commitPattern = /^[a-f0-9]{7,40}$/;

function parseArgs(argv) {
  const args = {
    check: false,
    plan: false,
    pretty: false,
    targetRef: null,
    targetTag: null,
    promotionCommit: null,
    help: false
  };
  for (let index = 0; index < argv.length; index += 1) {
    const arg = argv[index];
    if (arg === "--check") {
      args.check = true;
    } else if (arg === "--plan") {
      args.plan = true;
    } else if (arg === "--pretty") {
      args.pretty = true;
    } else if (arg === "--target-ref") {
      args.targetRef = requireValue(argv, index, arg);
      index += 1;
    } else if (arg === "--target-tag") {
      args.targetTag = requireValue(argv, index, arg);
      index += 1;
    } else if (arg === "--promotion-commit") {
      args.promotionCommit = requireValue(argv, index, arg);
      index += 1;
    } else if (arg === "--help" || arg === "-h") {
      args.help = true;
    } else {
      throw new Error(`unknown argument: ${arg}`);
    }
  }
  return args;
}

function requireValue(argv, index, arg) {
  const value = argv[index + 1];
  if (!value || value.startsWith("--")) {
    throw new Error(`${arg} requires a value`);
  }
  return value;
}

function usage() {
  return [
    "usage: node scripts/deploy-desired-state-plan.mjs [--plan] [--check] [--pretty] [--target-ref REF] [--target-tag TAG] [--promotion-commit SHA]",
    "",
    "Read-only DEV desired-state commit/image planner.",
    "",
    "Options:",
    "  --plan              explicitly request the default read-only JSON plan",
    "  --check             exit non-zero when desired-state sources are invalid, drifted, or do not match an explicit target",
    "  --pretty            print indented JSON",
    "  --target-ref REF    resolve REF with git and review promotion to its short commit tag",
    "  --target-tag TAG    review promotion to an explicit image tag without proving registry existence",
    "  --promotion-commit SHA",
    "                      require every authoritative desired-state commit/image/tag field to match this promotion commit",
    "",
    "This command reads repository files only. It does not build, pull, push, kubectl apply, restart services, or touch PROD."
  ].join("\n");
}

async function readJson(repoRoot, relativePath) {
  return readStructuredFile(repoRoot, relativePath);
}

async function readOptionalJson(repoRoot, relativePath) {
  try {
    return await readJson(repoRoot, relativePath);
  } catch {
    return null;
  }
}

function parseImageRef(image) {
  if (typeof image !== "string" || image.length === 0) {
    return { valid: false, repository: null, tag: null, reason: "image must be a non-empty string" };
  }
  if (image.includes("@")) {
    return { valid: false, repository: null, tag: null, reason: "digest-qualified image refs are not desired-state tags" };
  }
  const slashIndex = image.lastIndexOf("/");
  const colonIndex = image.lastIndexOf(":");
  if (colonIndex <= slashIndex) {
    return { valid: false, repository: image, tag: null, reason: "image ref is missing a tag" };
  }
  const repository = image.slice(0, colonIndex);
  const tag = image.slice(colonIndex + 1);
  if (!repository || !tag) {
    return { valid: false, repository, tag, reason: "image repository and tag must be non-empty" };
  }
  return { valid: true, repository, tag, reason: null };
}

function replaceImageTag(image, targetTag) {
  const parsed = parseImageRef(image);
  return parsed.valid ? `${parsed.repository}:${targetTag}` : null;
}

function shortCommit(value) {
  return typeof value === "string" && value.length >= 7 ? value.slice(0, 7) : value;
}

function commitEquivalent(actual, expected) {
  if (typeof actual !== "string" || typeof expected !== "string") return false;
  return actual === expected || actual === expected.slice(0, 7) || expected === actual.slice(0, 7);
}

function listItems(document) {
  if (!document) return [];
  return document.kind === "List" ? document.items ?? [] : [document];
}

function serviceIdForWorkload(item, container) {
  return (
    item?.metadata?.labels?.["hwlab.pikastech.local/service-id"] ||
    item?.spec?.template?.metadata?.labels?.["hwlab.pikastech.local/service-id"] ||
    container?.name ||
    item?.metadata?.name
  );
}

function workloadContainers(item) {
  return item?.spec?.template?.spec?.containers ?? [];
}

function envListToObject(envList) {
  const env = {};
  if (!Array.isArray(envList)) return env;
  for (const entry of envList) {
    if (!entry?.name) continue;
    if (Object.hasOwn(entry, "value")) {
      env[entry.name] = entry.value;
    } else if (entry.valueFrom) {
      env[entry.name] = { valueFrom: entry.valueFrom };
    }
  }
  return env;
}

function addDiagnostic(ctx, diagnostic) {
  ctx.diagnostics.push({
    severity: "blocker",
    ...diagnostic
  });
}

function addMismatch(ctx, code, sourcePath, message, expected, actual, extra = {}) {
  addDiagnostic(ctx, {
    code,
    path: sourcePath,
    message,
    expected,
    actual,
    ...extra
  });
}

function recordObservation(ctx, observation) {
  ctx.observations.push(observation);
}

function expectedEnvValue(name, service, desiredCommitId) {
  if (name === "HWLAB_COMMIT_ID" || name === "HWLAB_SKILLS_COMMIT_ID") return service?.commitId ?? service?.imageTag ?? desiredCommitId;
  if (name === "HWLAB_IMAGE") return service?.image ?? null;
  if (name === "HWLAB_IMAGE_TAG") return service?.imageTag ?? null;
  if (name === "HWLAB_IMAGE_DIGEST") return service?.digest ?? "not_published";
  return null;
}

function observationKindForEnv(name) {
  if (commitMirrorEnvNames.has(name)) return "commit";
  if (imageMirrorEnvNames.has(name)) return "image";
  if (tagMirrorEnvNames.has(name)) return "tag";
  if (digestMirrorEnvNames.has(name)) return "digest";
  return "unknown";
}

function valueMatchesExpected(kind, actual, expected) {
  if (expected === null || expected === undefined) return true;
  if (kind === "commit") return commitEquivalent(actual, expected);
  return Object.is(actual, expected);
}

function inspectEnvMirrors(ctx, { source, basePath, serviceId, env, service, desiredCommitId, requiredMirrorNames = [] }) {
  const present = {};
  const missing = [];
  const diagnostics = [];
  const required = new Set(requiredMirrorNames);

  for (const name of mirrorEnvNames) {
    if (!Object.hasOwn(env ?? {}, name)) {
      missing.push(name);
      if (required.has(name)) {
        const kind = observationKindForEnv(name);
        const expected = expectedEnvValue(name, service, desiredCommitId);
        const diagnostic = {
          code: "missing_mirror",
          path: `${basePath}.${name}`,
          message: `${source} ${serviceId} must include ${name} so DEV applies own runtime ${kind} identity`,
          expected,
          actual: null
        };
        diagnostics.push(diagnostic);
        addDiagnostic(ctx, diagnostic);
      }
      continue;
    }
    const actual = env[name];
    const kind = observationKindForEnv(name);
    const expected = expectedEnvValue(name, service, desiredCommitId);
    const record = {
      name,
      value: actual,
      expected,
      status: valueMatchesExpected(kind, actual, expected) ? "match" : "drift"
    };
    present[name] = record;
    recordObservation(ctx, {
      source,
      serviceId,
      path: `${basePath}.${name}`,
      field: name,
      kind,
      value: actual,
      expected
    });
    if (record.status === "drift") {
      const message = `${source} ${serviceId} ${name} mirror does not match desired ${kind}`;
      const diagnostic = {
        code: "mirror_mismatch",
        path: `${basePath}.${name}`,
        message,
        expected,
        actual
      };
      diagnostics.push(diagnostic);
      addDiagnostic(ctx, diagnostic);
    }
  }

  return { present, missing, diagnostics };
}

function inspectCodeAgentProviderDesiredState(ctx, deployService, workloadRecords) {
  const workloadRecord = workloadRecords.find((workload) => workload.containerName === "hwlab-cloud-api") ?? workloadRecords[0] ?? null;
  const inspection = inspectCodeAgentProviderManifestRefs({
    deployEnv: deployService?.env ?? {},
    workloadEnv: workloadRecord?.env ?? {}
  });
  if (!inspection.ready) {
    addDiagnostic(ctx, {
      code: "code_agent_provider_contract_mismatch",
      path: "deploy.codeAgentProvider",
      message: "hwlab-cloud-api desired state must preserve Code Agent provider Secret ref and DEV egress proxy env",
      expected: {
        provider: DEV_CODE_AGENT_PROVIDER_CONTRACT.codeAgentProvider,
        secretRef: `${DEV_CODE_AGENT_PROVIDER_CONTRACT.secretRefs[0].secretName}/${DEV_CODE_AGENT_PROVIDER_CONTRACT.secretRefs[0].secretKey}`,
        egressTarget: DEV_CODE_AGENT_PROVIDER_CONTRACT.egress.target
      },
      actual: {
        missingDeployEnv: inspection.missingDeployEnv,
        missingWorkloadEnv: inspection.missingWorkloadEnv,
        deployMismatches: inspection.deployMismatches.map((entry) => entry.name),
        workloadMismatches: inspection.workloadMismatches.map((entry) => entry.name),
        missingSecretRefs: inspection.missingSecretRefs,
        missingEgressContract: inspection.missingEgressContract,
        missingCodexHomeContract: inspection.missingCodexHomeContract,
        missingWorkspaceContract: inspection.missingWorkspaceContract,
        missingNoProxyContract: inspection.missingNoProxyContract,
        missingForwarderContract: inspection.missingForwarderContract
      },
      secretMaterialRead: false,
      valuesRedacted: true
    });
  }
  recordObservation(ctx, {
    source: "deploy",
    serviceId: "hwlab-cloud-api",
    path: `${deployPath}.services.hwlab-cloud-api.env.OPENAI_API_KEY`,
    field: "OPENAI_API_KEY",
    kind: "secret-ref",
    value: inspection.secretRef.present ? "secretRef:declared" : "missing_or_mismatched",
    expected: "secretRef:hwlab-code-agent-provider/openai-api-key"
  });
  recordObservation(ctx, {
    source: "workload",
    serviceId: "hwlab-cloud-api",
    path: `${workloadsPath}.hwlab-cloud-api.env.HWLAB_CODE_AGENT_OPENAI_BASE_URL`,
    field: "HWLAB_CODE_AGENT_OPENAI_BASE_URL",
    kind: "codex-api-forwarder",
    value: inspection.egress.workloadMatchesDevProxy ? "pod-local-codex-api-forwarder" : "missing_or_mismatched",
    expected: DEV_CODE_AGENT_PROVIDER_CONTRACT.egress.target
  });
  return inspection;
}

function dbSecretRefPlaceholder() {
  const ref = DEV_DB_ENV_CONTRACT.secretRefs[0];
  return `secretRef:${ref.secretName}/${ref.secretKey}`;
}

function secretRefMatches(entry, ref) {
  return (
    entry?.valueFrom?.secretKeyRef?.name === ref.secretName &&
    entry?.valueFrom?.secretKeyRef?.key === ref.secretKey
  );
}

function inspectCloudApiDbDesiredState(ctx, deployService, workloadRecords) {
  const workloadRecord = workloadRecords.find((workload) => workload.containerName === "hwlab-cloud-api") ?? workloadRecords[0] ?? null;
  const deployEnv = deployService?.env ?? {};
  const workloadEnv = workloadRecord?.env ?? {};
  const secretRef = DEV_DB_ENV_CONTRACT.secretRefs[0];
  const expectedSecretRef = dbSecretRefPlaceholder();
  const expectedSslMode = DEV_DB_ENV_CONTRACT.nonSecretDefaults.HWLAB_CLOUD_DB_SSL_MODE;
  const deploySecretMatches = deployEnv.HWLAB_CLOUD_DB_URL === expectedSecretRef;
  const workloadSecretMatches = secretRefMatches(workloadEnv.HWLAB_CLOUD_DB_URL, secretRef);
  const deploySslMatches = deployEnv.HWLAB_CLOUD_DB_SSL_MODE === expectedSslMode;
  const workloadSslMatches = workloadEnv.HWLAB_CLOUD_DB_SSL_MODE === expectedSslMode;
  const ready = deploySecretMatches && workloadSecretMatches && deploySslMatches && workloadSslMatches;

  if (!ready) {
    addDiagnostic(ctx, {
      code: "cloud_api_db_contract_mismatch",
      path: "deploy.cloudApiDb",
      message: "hwlab-cloud-api desired state must preserve DEV DB Secret ref and non-SSL runtime mode",
      expected: {
        secretRef: `${secretRef.secretName}/${secretRef.secretKey}`,
        sslMode: expectedSslMode
      },
      actual: {
        deploySecretRef: deploySecretMatches ? "secretRef:declared" : "missing_or_mismatched",
        workloadSecretRef: workloadSecretMatches ? "secretRef:declared" : "missing_or_mismatched",
        deploySslMode: deployEnv.HWLAB_CLOUD_DB_SSL_MODE ?? null,
        workloadSslMode: workloadEnv.HWLAB_CLOUD_DB_SSL_MODE ?? null
      },
      secretMaterialRead: false,
      valuesRedacted: true
    });
  }

  recordObservation(ctx, {
    source: "deploy",
    serviceId: "hwlab-cloud-api",
    path: `${deployPath}.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_SSL_MODE`,
    field: "HWLAB_CLOUD_DB_SSL_MODE",
    kind: "runtime-config",
    value: deployEnv.HWLAB_CLOUD_DB_SSL_MODE ?? null,
    expected: expectedSslMode
  });
  recordObservation(ctx, {
    source: "workload",
    serviceId: "hwlab-cloud-api",
    path: `${workloadsPath}.hwlab-cloud-api.env.HWLAB_CLOUD_DB_SSL_MODE`,
    field: "HWLAB_CLOUD_DB_SSL_MODE",
    kind: "runtime-config",
    value: workloadEnv.HWLAB_CLOUD_DB_SSL_MODE ?? null,
    expected: expectedSslMode
  });

  return {
    contractVersion: DEV_DB_ENV_CONTRACT.contractVersion,
    environment: DEV_DB_ENV_CONTRACT.environment,
    status: ready ? "pass" : "blocked",
    ready,
    secretRef: {
      env: secretRef.env,
      secretName: secretRef.secretName,
      secretKey: secretRef.secretKey,
      deployPlaceholderPresent: deploySecretMatches,
      workloadSecretRefPresent: workloadSecretMatches,
      present: deploySecretMatches && workloadSecretMatches,
      redacted: true
    },
    sslMode: {
      env: "HWLAB_CLOUD_DB_SSL_MODE",
      value: deployEnv.HWLAB_CLOUD_DB_SSL_MODE ?? null,
      workloadValue: workloadEnv.HWLAB_CLOUD_DB_SSL_MODE ?? null,
      expected: expectedSslMode,
      deployMatches: deploySslMatches,
      workloadMatches: workloadSslMatches,
      secret: false
    },
    secretMaterialRead: false,
    valuesRedacted: true,
    liveDbEvidence: false,
    fixtureEvidence: false
  };
}

function indexWorkloads(workloads) {
  const byService = new Map();
  for (const item of listItems(workloads)) {
    for (const [containerIndex, container] of workloadContainers(item).entries()) {
      const serviceId = serviceIdForWorkload(item, container);
      if (!serviceId) continue;
      const record = {
        kind: item?.kind ?? "unknown",
        name: item?.metadata?.name ?? "unknown",
        namespace: item?.metadata?.namespace ?? null,
        containerIndex,
        containerName: container?.name ?? "unknown",
        image: container?.image ?? null,
        env: {
          ...envListToObject(container?.env),
          __volumeMounts: Array.isArray(container?.volumeMounts) ? container.volumeMounts : [],
          __volumes: Array.isArray(item?.spec?.template?.spec?.volumes) ? item.spec.template.spec.volumes : [],
          __podContainers: Array.isArray(item?.spec?.template?.spec?.containers) ? item.spec.template.spec.containers : [],
          __initContainers: Array.isArray(item?.spec?.template?.spec?.initContainers) ? item.spec.template.spec.initContainers : [],
          __deploymentStrategy: item?.spec?.strategy ?? null
        },
        path: `${workloadsPath}.${item?.kind ?? "Workload"}.${item?.metadata?.name ?? serviceId}.containers[${containerIndex}]`
      };
      const list = byService.get(serviceId) ?? [];
      list.push(record);
      byService.set(serviceId, list);
    }
  }
  return byService;
}

function serviceIdsFrom(...sources) {
  const ids = [];
  for (const source of sources) {
    for (const id of source) {
      if (typeof id === "string" && id.length > 0 && !ids.includes(id)) ids.push(id);
    }
  }
  return ids;
}

function assertTargetTag(ctx, tag, pathName) {
  if (!tagPattern.test(tag)) {
    addMismatch(ctx, "invalid_target_tag", pathName, "target tag is not a valid Docker tag", "Docker tag syntax", tag);
  }
  if (mutableTags.has(tag)) {
    addMismatch(ctx, "mutable_target_tag", pathName, `target tag ${tag} is mutable and forbidden for desired-state pinning`, "immutable tag", tag);
  }
}

async function resolveTarget(repoRoot, { targetRef, targetTag, promotionCommit, requireTargetConvergence = false }, ctx) {
  if (!targetRef && !targetTag && !promotionCommit) return null;
  let commitId = null;
  let shortCommitId = null;
  let acceptedCurrentMain = null;
  if (targetRef) {
    try {
      const result = await execFileAsync("git", ["rev-parse", "--verify", `${targetRef}^{commit}`], { cwd: repoRoot });
      commitId = result.stdout.trim();
      shortCommitId = commitId.slice(0, 7);
    } catch (error) {
      addDiagnostic(ctx, {
        code: "target_ref_unresolved",
        path: "--target-ref",
        message: `target ref ${targetRef} could not be resolved with git`,
        expected: "local git ref",
        actual: error instanceof Error ? error.message : String(error)
      });
    }
  }
  if (promotionCommit) {
    if (!commitPattern.test(String(promotionCommit))) {
      addMismatch(ctx, "invalid_promotion_commit", "--promotion-commit", "promotion commit must be a short or full lowercase Git SHA", "7-40 lowercase hex", promotionCommit);
    } else {
      const promotionShortCommitId = promotionCommit.slice(0, 7);
      if (commitId && !commitEquivalent(promotionCommit, commitId)) {
        addMismatch(ctx, "promotion_commit_ref_mismatch", "--promotion-commit", "promotion commit must match the resolved target ref", commitId, promotionCommit);
      }
      commitId = commitId ?? promotionCommit;
      shortCommitId = shortCommitId ?? promotionShortCommitId;
    }
  }
  const tag = targetTag ?? shortCommitId;
  if (tag) assertTargetTag(ctx, tag, "--target-tag");
  if (promotionCommit && commitPattern.test(String(promotionCommit)) && targetTag && targetTag !== promotionCommit.slice(0, 7)) {
    addMismatch(ctx, "promotion_tag_mismatch", "--target-tag", "promotion target tag must be the short promotion commit", promotionCommit.slice(0, 7), targetTag);
  }
  const requireConvergence = Boolean(promotionCommit || requireTargetConvergence);
  if (targetRef && commitId && requireConvergence && !promotionCommit && !targetTag) {
    acceptedCurrentMain = await resolveAcceptedCurrentMain(repoRoot, commitId);
  }
  const comparisonCommitId = acceptedCurrentMain?.commitId ?? commitId;
  const comparisonTag = acceptedCurrentMain?.tag ?? tag;
  return {
    targetRef,
    promotionCommit,
    commitId,
    shortCommitId,
    tag,
    tagSource: targetTag ? "target-tag" : promotionCommit ? "promotion-commit-short" : "target-ref-short-commit",
    acceptedCurrentMain,
    comparisonCommitId,
    comparisonTag,
    requireConvergence,
    convergenceRequirement: promotionCommit ? "promotion-commit" : requireTargetConvergence ? "target-check" : "plan-only"
  };
}

async function resolveAcceptedCurrentMain(repoRoot, targetCommitId) {
  try {
    const [{ stdout: headStdout }, { stdout: parentsStdout }] = await Promise.all([
      execFileAsync("git", ["rev-parse", "--verify", "HEAD^{commit}"], { cwd: repoRoot }),
      execFileAsync("git", ["rev-list", "--parents", "-n", "1", targetCommitId], { cwd: repoRoot })
    ]);
    const headCommitId = headStdout.trim();
    if (headCommitId !== targetCommitId) return null;

    const [commitId, firstParentId, secondParentId] = parentsStdout.trim().split(/\s+/u);
    if (commitId !== targetCommitId || !firstParentId || !secondParentId) return null;

    return {
      mode: "target-ref-head-merge-first-parent",
      reason: "A merged main commit cannot contain desired-state files pinned to its own content hash; compare against the accepted first-parent main commit.",
      commitId: firstParentId,
      shortCommitId: firstParentId.slice(0, 7),
      tag: firstParentId.slice(0, 7)
    };
  } catch {
    return null;
  }
}

function targetLabel(target) {
  if (target.acceptedCurrentMain) return `${target.targetRef} accepted first parent (${target.acceptedCurrentMain.tag})`;
  if (target.targetRef && target.tag) return `${target.targetRef} (${target.tag})`;
  if (target.promotionCommit) return target.promotionCommit;
  if (target.tag) return `tag ${target.tag}`;
  return "target";
}

function observationMatchesTarget(observation, target) {
  if (!target) return null;
  if (observation.kind === "commit") {
    return target.comparisonCommitId ? commitEquivalent(observation.value, target.comparisonCommitId) : null;
  }
  if (observation.kind === "tag") {
    return target.comparisonTag ? observation.value === target.comparisonTag : null;
  }
  if (observation.kind === "image") {
    return target.comparisonTag ? parseImageRef(observation.value).tag === target.comparisonTag : null;
  }
  return null;
}

function buildTargetConvergence(ctx, target) {
  if (!target) return null;
  if (target.targetRef && !target.commitId) {
    return {
      state: "target_unresolved",
      comparableFields: 0,
      matchingTargetFields: 0,
      pendingTargetFields: 0,
      matchingPaths: [],
      pendingPaths: []
    };
  }
  const comparable = ctx.observations
    .map((observation) => ({
      ...observation,
      matchesTarget: observationMatchesTarget(observation, target)
    }))
    .filter((observation) => observation.matchesTarget !== null);
  const matching = comparable.filter((observation) => observation.matchesTarget === true);
  const pending = comparable.filter((observation) => observation.matchesTarget === false);
  const partial = matching.length > 0 && pending.length > 0;
  const promotionRequired = target.requireConvergence === true;

  if (partial) {
    addDiagnostic(ctx, {
      code: "partial_target_drift",
      path: "desired-state",
      message: "desired-state fields partially match the target while other authoritative fields still point elsewhere",
      expected: target.tag,
      actual: {
        matching: matching.length,
        pending: pending.length
      }
    });
  } else if (promotionRequired && pending.length > 0) {
    const promotionCommitRequired = Boolean(target.promotionCommit);
    addDiagnostic(ctx, {
      code: promotionCommitRequired ? "promotion_commit_mismatch" : "target_desired_state_mismatch",
      path: "desired-state",
      message: promotionCommitRequired
        ? "desired-state fields do not match the required promotion commit/tag"
        : `stale artifact catalog is not pinned to ${targetLabel(target)}; explicit target checks require current-main/commit-pinned catalog identity`,
      expected: {
        commitId: target.comparisonCommitId,
        imageTag: target.comparisonTag
      },
      actual: {
        pending: pending.length,
        samplePendingPaths: pending.slice(0, 8).map((item) => item.path)
      }
    });
  }

  const state = partial
    ? "partial_drift"
    : pending.length === 0
      ? target.acceptedCurrentMain
        ? "accepted_main_promoted"
        : "already_promoted"
      : promotionRequired
        ? target.promotionCommit
          ? "promotion_mismatch"
          : "target_mismatch"
        : "promotion_pending";
  return {
    state,
    comparableFields: comparable.length,
    matchingTargetFields: matching.length,
    pendingTargetFields: pending.length,
    matchingPaths: matching.map((item) => item.path),
    pendingPaths: pending.map((item) => item.path)
  };
}

function reportHints(report, desiredCommitId) {
  if (!report) {
    return {
      path: artifactReportPath,
      present: false,
      authoritative: false,
      note: "optional report snapshot is absent; desired-state review still uses deploy files only"
    };
  }
  const artifactPublish = report.artifactPublish ?? {};
  const reportCommitId = report.commitId ?? null;
  const artifactSourceCommitId = artifactPublish.sourceCommitId ?? null;
  return {
    path: artifactReportPath,
    present: true,
    authoritative: false,
    note: "temporary publish JSON is contextual evidence only; deploy desired-state remains authoritative in deploy/",
    commitId: reportCommitId,
    matchesDesiredState: reportCommitId ? commitEquivalent(reportCommitId, desiredCommitId) : null,
    artifactPublish: {
      status: artifactPublish.status ?? null,
      sourceCommitId: artifactSourceCommitId,
      sourceMatchesDesiredState: artifactSourceCommitId ? commitEquivalent(artifactSourceCommitId, desiredCommitId) : null,
      publishedCount: artifactPublish.publishedCount ?? null,
      serviceCount: artifactPublish.serviceCount ?? null,
      registryVerified: artifactPublish.registryVerified ?? null,
      registryCapabilitiesClassification: artifactPublish.registryCapabilities?.classification ?? null
    }
  };
}

function servicePromotion(service, target) {
  const targetTag = target?.comparisonTag ?? target?.tag;
  if (!targetTag) return null;
  return {
    targetCommitId: target.comparisonCommitId ?? target.commitId,
    targetImageTag: targetTag,
    deployImage: null,
    catalogImage: service.catalog?.image ? replaceImageTag(service.catalog.image, targetTag) : null,
    workloadImages: (service.workloads ?? []).map((workload) => ({
      workload: workload.name,
      container: workload.container,
      image: workload.image ? replaceImageTag(workload.image, targetTag) : null
    }))
  };
}

function targetCommands(target) {
  if (!target?.targetRef && !target?.promotionCommit) return [];
  const commands = [];
  if (target.promotionCommit) {
    commands.push(`node scripts/deploy-desired-state-plan.mjs --promotion-commit ${target.promotionCommit} --check --pretty`);
  }
  if (target.targetRef) {
    commands.push(
    `node scripts/deploy-desired-state-plan.mjs --target-ref ${target.targetRef} --pretty`,
    `node scripts/refresh-artifact-catalog.mjs --target-ref ${target.targetRef} --blocked --no-write`,
    `node scripts/refresh-artifact-catalog.mjs --target-ref ${target.targetRef} --publish-report ${artifactReportPath} --no-write`
    );
  }
  return commands;
}

export async function buildDesiredStatePlan(options = {}) {
  const repoRoot = options.repoRoot ?? defaultRepoRoot;
  const ctx = { diagnostics: [], observations: [] };
  const [deploy, catalog, workloads, artifactReport] = await Promise.all([
    readJson(repoRoot, deployPath),
    readJson(repoRoot, catalogPath),
    readJson(repoRoot, workloadsPath),
    readOptionalJson(repoRoot, artifactReportPath)
  ]);
  const target = await resolveTarget(repoRoot, {
    targetRef: options.targetRef ?? null,
    targetTag: options.targetTag ?? null,
    promotionCommit: options.promotionCommit ?? null,
    requireTargetConvergence: options.requireTargetConvergence === true
  }, ctx);

  const deployServices = deploy.services ?? [];
  const catalogServices = catalog.services ?? [];
  const deployByService = new Map(deployServices.map((service) => [service.serviceId, service]));
  const catalogByService = new Map(catalogServices.map((service) => [service.serviceId, service]));
  const workloadByService = indexWorkloads(workloads);
  const desiredCommitId = catalog.commitId;
  const desiredImageTag = shortCommit(desiredCommitId);

  recordObservation(ctx, {
    source: "catalog",
    path: `${catalogPath}.commitId`,
    field: "commitId",
    kind: "commit",
    value: catalog.commitId,
    expected: desiredCommitId
  });

  if (Object.hasOwn(deploy, "commitId")) {
    addMismatch(ctx, "generated_artifact_identity", `${deployPath}.commitId`, "deploy.yaml is human-authored config; generated commit identity must stay in artifact catalog", "absent", deploy.commitId);
  }
  if (!commitPattern.test(String(catalog.commitId ?? ""))) {
    addMismatch(ctx, "invalid_commit", `${catalogPath}.commitId`, "artifact catalog commitId must be a short or full lowercase Git SHA", "7-40 lowercase hex", catalog.commitId);
  }

  const serviceIds = serviceIdsFrom(
    deployServices.map((service) => service.serviceId),
    catalogServices.map((service) => service.serviceId),
    [...workloadByService.keys()]
  );

  const services = [];
  let codeAgentProvider = null;
  let cloudApiDb = null;
  for (const serviceId of serviceIds) {
    const deployService = deployByService.get(serviceId);
    const catalogService = catalogByService.get(serviceId);
    const workloadsForService = workloadByService.get(serviceId) ?? [];
    const service = {
      serviceId,
      deploy: null,
      catalog: null,
      workloads: [],
      promotion: null
    };

    if (!deployService) {
      addMismatch(ctx, "missing_service", `${deployPath}.services.${serviceId}`, `${serviceId} missing from deploy manifest`, "service entry", null, { serviceId });
    }
    if (!catalogService) {
      addMismatch(ctx, "missing_service", `${catalogPath}.services.${serviceId}`, `${serviceId} missing from artifact catalog`, "service entry", null, { serviceId });
    }
    if (!workloadsForService.length) {
      addMismatch(ctx, "missing_workload", `${workloadsPath}.${serviceId}`, `${serviceId} missing from k8s workloads`, "workload container", null, { serviceId });
    }

    const catalogImage = parseImageRef(catalogService?.image);
    const serviceImageTag = catalogService?.imageTag ?? catalogImage.tag ?? desiredImageTag;
    const serviceCommitId = catalogService?.commitId ?? serviceImageTag;
    if (deployService) {
      const deployPathBase = `${deployPath}.services.${serviceId}`;
      for (const field of Object.keys(deployService)) {
        if (forbiddenDeployArtifactFields.has(field)) {
          addMismatch(ctx, "generated_artifact_identity", `${deployPathBase}.${field}`, `${serviceId} deploy service field is generated artifact identity and must stay in artifact catalog`, "absent", deployService[field], { serviceId });
        }
      }
      for (const envName of Object.keys(deployService.env ?? {})) {
        if (forbiddenDeployArtifactEnv.has(envName)) {
          addMismatch(ctx, "generated_artifact_identity", `${deployPathBase}.env.${envName}`, `${serviceId} deploy env is generated artifact identity and must be rendered from artifact catalog`, "absent", deployService.env[envName], { serviceId });
        }
      }
      service.deploy = {
        artifactIdentity: "human-authored-config",
        envMirrors: inspectEnvMirrors(ctx, {
          source: "deploy",
          basePath: `${deployPathBase}.env`,
          serviceId,
          env: deployService.env ?? {},
          service: null,
          desiredCommitId: null,
          requiredMirrorNames: []
        })
      };
    }

    if (catalogService) {
      const catalogPathBase = `${catalogPath}.services.${serviceId}`;
      recordObservation(ctx, {
        source: "catalog",
        serviceId,
        path: `${catalogPathBase}.commitId`,
        field: "commitId",
        kind: "commit",
        value: catalogService.commitId,
        expected: serviceCommitId
      });
      recordObservation(ctx, {
        source: "catalog",
        serviceId,
        path: `${catalogPathBase}.image`,
        field: "image",
        kind: "image",
        value: catalogService.image,
        expected: catalogService.image
      });
      recordObservation(ctx, {
        source: "catalog",
        serviceId,
        path: `${catalogPathBase}.imageTag`,
        field: "imageTag",
        kind: "tag",
        value: catalogService.imageTag,
        expected: serviceImageTag
      });
      if (!commitPattern.test(String(catalogService.commitId ?? ""))) {
        addMismatch(ctx, "invalid_commit", `${catalogPathBase}.commitId`, `${serviceId} catalog service commitId must be a short or full lowercase Git SHA`, "7-40 lowercase hex", catalogService.commitId, { serviceId });
      } else if (!commitEquivalent(catalogService.commitId, serviceImageTag)) {
        addMismatch(ctx, "commit_mismatch", `${catalogPathBase}.commitId`, `${serviceId} catalog service commitId must match service artifact tag`, serviceImageTag, catalogService.commitId, { serviceId });
      }
      if (!catalogImage.valid) {
        addMismatch(ctx, "invalid_image", `${catalogPathBase}.image`, catalogImage.reason, "tagged image ref", catalogService.image, { serviceId });
      }
      if (catalogService.imageTag !== serviceImageTag) {
        addMismatch(ctx, "image_tag_mismatch", `${catalogPathBase}.imageTag`, `${serviceId} catalog imageTag must match service artifact tag`, serviceImageTag, catalogService.imageTag, { serviceId });
      }
      if (catalogImage.valid && catalogImage.tag !== catalogService.imageTag) {
        addMismatch(ctx, "image_tag_mismatch", `${catalogPathBase}.image`, `${serviceId} catalog image tag must match catalog imageTag`, catalogService.imageTag, catalogImage.tag, { serviceId });
      }
      service.catalog = {
        commitId: catalogService.commitId,
        image: catalogService.image,
        imageTag: catalogService.imageTag,
        digest: catalogService.digest,
        publishState: catalogService.publishState,
        artifactRequired: catalogService.artifactRequired
      };
    }

    for (const workload of workloadsForService) {
      const workloadImage = parseImageRef(workload.image);
      recordObservation(ctx, {
        source: "workload",
        serviceId,
        path: `${workload.path}.image`,
        field: "image",
        kind: "image",
        value: workload.image,
        expected: catalogService?.image ?? null
      });
      if (!workloadImage.valid) {
        addMismatch(ctx, "invalid_image", `${workload.path}.image`, workloadImage.reason, "tagged image ref", workload.image, { serviceId });
      }
      service.workloads.push({
        kind: workload.kind,
        name: workload.name,
        namespace: workload.namespace,
        container: workload.containerName,
        image: workload.image,
        imageTag: workloadImage.tag,
        envMirrors: inspectEnvMirrors(ctx, {
          source: "workload",
          basePath: `${workload.path}.env`,
          serviceId,
          env: workload.env,
          service: null,
          desiredCommitId: null,
          requiredMirrorNames: []
        })
      });
    }

    service.promotion = servicePromotion(service, target);
    services.push(service);

    if (serviceId === "hwlab-cloud-api") {
      cloudApiDb = inspectCloudApiDbDesiredState(ctx, deployService, workloadsForService);
      codeAgentProvider = inspectCodeAgentProviderDesiredState(ctx, deployService, workloadsForService);
    }
  }

  const targetConvergence = buildTargetConvergence(ctx, target);
  const blockers = ctx.diagnostics.filter((diagnostic) => diagnostic.severity === "blocker");
  const status = blockers.length > 0
    ? "blocked"
    : targetConvergence?.state === "promotion_pending"
      ? "planned"
      : "pass";

  const presentMirrorCount = services.reduce((count, service) => {
    const deployCount = service.deploy ? Object.keys(service.deploy.envMirrors.present).length : 0;
    const workloadCount = service.workloads.reduce((inner, workload) => inner + Object.keys(workload.envMirrors.present).length, 0);
    return count + deployCount + workloadCount;
  }, 0);

  return {
    kind: "hwlab-deploy-desired-state-plan",
    mode: "read-only-plan",
    status,
    source: {
      deploy: deployPath,
      artifactCatalog: catalogPath,
      workloads: workloadsPath,
      optionalReport: artifactReportPath
    },
    safety: {
      readOnly: true,
      sourceOrDryRunSupportOnly: true,
      kubectlApply: false,
      registryPull: false,
      registryPush: false,
      imageBuild: false,
      serviceRestart: false,
      prod: false,
      devLiveClaim: false
    },
    checkSemantics: "--check exits non-zero for missing/invalid desired-state sources, generated artifact identity leaking into deploy.yaml, invalid target tags, partial target drift, or catalog identity mismatch with an explicit --target-ref, --target-tag, or --promotion-commit. When the checked target ref is the current checked-out merge commit, the comparable current-main target is its accepted first parent because a commit cannot embed its own content hash. Without --check, a uniform older catalog under --target-ref/--target-tag is a read-only promotion plan.",
    summary: {
      desiredCommitId,
      desiredImageTag,
      artifactState: catalog.artifactState,
      ciPublished: catalog.publish?.ciPublished === true,
      registryVerified: catalog.publish?.registryVerified === true,
      services: services.length,
      workloadContainers: [...workloadByService.values()].reduce((sum, entries) => sum + entries.length, 0),
      presentMirrorCount,
      diagnostics: ctx.diagnostics.length,
      blockers: blockers.length,
      targetConvergence: targetConvergence?.state ?? "not_requested"
    },
    target: target ? {
      ...target,
      convergence: targetConvergence,
      commands: targetCommands(target)
    } : null,
    promotionBoundary: {
      writes: [],
      authoritativeDesiredState: [deployPath, catalogPath, workloadsPath],
      humanAuthoredTruth: [deployPath, workloadsPath],
      artifactIdentityTruth: [catalogPath],
      nonAuthoritativeEvidence: [artifactReportPath],
      note: "This planner is read-only. deploy.yaml is human-authored config; artifact-catalog carries generated image identity and is written by node Tekton promotion only to the configured GitOps branch. It does not prove image existence, registry reachability, a real DEV apply, or M3 DEV-LIVE hardware-loop evidence."
    },
    cloudApiDb: cloudApiDb ?? {
      status: "blocked",
      ready: false,
      secretMaterialRead: false,
      valuesRedacted: true,
      liveDbEvidence: false
    },
    codeAgentProvider: codeAgentProvider ?? {
      status: "blocked",
      ready: false,
      missingDeployEnv: ["hwlab-cloud-api"],
      missingWorkloadEnv: ["hwlab-cloud-api"],
      secretMaterialRead: false,
      valuesRedacted: true
    },
    reportHints: reportHints(artifactReport, desiredCommitId),
    services,
    diagnostics: ctx.diagnostics
  };
}

export async function runDeployDesiredStatePlanCli(argv = process.argv.slice(2), options = {}) {
  const args = parseArgs(argv);
  if (args.help) {
    process.stdout.write(`${usage()}\n`);
    return;
  }
  const plan = await buildDesiredStatePlan({
    repoRoot: options.repoRoot,
    targetRef: args.targetRef,
    targetTag: args.targetTag,
    promotionCommit: args.promotionCommit,
    requireTargetConvergence: args.check && Boolean(args.targetRef || args.targetTag)
  });
  process.stdout.write(`${JSON.stringify(plan, null, args.pretty ? 2 : 0)}\n`);
  if (args.check && plan.status === "blocked") {
    process.exitCode = 1;
  }
}

export function writeDeployDesiredStatePlanError(error) {
  process.stdout.write(`${JSON.stringify({
    kind: "hwlab-deploy-desired-state-plan",
    mode: "read-only-plan",
    status: "error",
    error: error instanceof Error ? error.message : String(error),
    safety: {
      readOnly: true,
      kubectlApply: false,
      registryPull: false,
      registryPush: false,
      imageBuild: false,
      serviceRestart: false,
      prod: false,
      devLiveClaim: false
    }
  })}\n`);
  process.exitCode = 1;
}
