import { readFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { rootPath } from "./src/config";
import { runArtifactRegistryCommand } from "./src/artifact-registry";

type JsonRecord = Record<string, unknown>;

const serviceId = "todo-note";
const commit = "a14ce0eb855a685fa17b47adacd54623e72cd2ff";
const sourceRepo = "https://gitee.com/Lyon1998/todo_note";

function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
  if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
}

function asRecord(value: unknown, label: string): JsonRecord {
  assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), `${label} must be an object`, value);
  return value as JsonRecord;
}

function asArray(value: unknown, label: string): unknown[] {
  assertCondition(Array.isArray(value), `${label} must be an array`, value);
  return value as unknown[];
}

function runDeployApplyDryRun(): JsonRecord {
  const result = spawnSync("bun", ["scripts/cli.ts", "deploy", "apply", "--env", "prod", "--service", serviceId, "--dry-run"], {
    cwd: process.cwd(),
    encoding: "utf8",
    maxBuffer: 8 * 1024 * 1024,
  });
  assertCondition(result.status === 0, "deploy apply dry-run should exit 0", {
    status: result.status,
    stdout: result.stdout.slice(-2000),
    stderr: result.stderr.slice(-2000),
  });
  const envelope = asRecord(JSON.parse(result.stdout) as unknown, "deploy apply envelope");
  assertCondition(envelope.ok === true, "deploy apply dry-run envelope should be ok", envelope);
  const data = asRecord(envelope.data, "deploy apply data");
  const results = asArray(data.results, "deploy apply results");
  assertCondition(results.length === 1, "deploy apply dry-run should include one result", data);
  return asRecord(results[0], "todo-note deploy apply result");
}

function assertTodoNoteComposeYaml(): void {
  const compose = readFileSync(rootPath("docker-compose.yml"), "utf8");
  const todoNoteSection = compose.slice(compose.indexOf("  todo-note:"), compose.indexOf("  oa-event-flow:"));
  assertCondition(todoNoteSection.includes("image: todo-note"), "todo-note Compose service must name a stable image", todoNoteSection);
  for (const expected of [
    "unidesk.ai/deploy-service-id",
    "unidesk.ai/deploy-ref",
    "unidesk.ai/deploy-repo",
    "unidesk.ai/deploy-commit",
    "unidesk.ai/deploy-requested-commit",
    "UNIDESK_DEPLOY_COMMIT",
    "UNIDESK_DEPLOY_REQUESTED_COMMIT",
  ]) {
    assertCondition(todoNoteSection.includes(expected), `todo-note Compose section must include ${expected}`, todoNoteSection);
  }
}

function assertPlan(plan: JsonRecord, label: string): void {
  const target = asRecord(plan.target, `${label} target`);
  const source = asRecord(plan.source, `${label} source`);
  const build = asRecord(plan.build, `${label} build`);
  const labels = asRecord(plan.requiredLabels, `${label} requiredLabels`);
  const validation = asArray(plan.validation, `${label} validation`).map(String);
  const runtimeProof = asRecord(plan.runtimeProof, `${label} runtimeProof`);
  const sources = asArray(runtimeProof.sources, `${label} runtimeProof.sources`).map(String);
  const requiredEnvKeys = asArray(runtimeProof.requiredEnvKeys, `${label} runtimeProof.requiredEnvKeys`).map(String);

  assertCondition(plan.ok === true && plan.supported === true, `${label} plan must be supported`, plan);
  assertCondition(plan.dryRun === true && plan.mutation === false, `${label} plan must be non-mutating`, plan);
  assertCondition(plan.environment === "prod", `${label} environment mismatch`, plan);
  assertCondition(plan.serviceId === serviceId, `${label} service id mismatch`, plan);
  assertCondition(plan.commit === commit, `${label} commit mismatch`, plan);
  assertCondition(plan.sourceRepo === sourceRepo, `${label} source repo mismatch`, plan);
  assertCondition(source.repo === sourceRepo && source.commit === commit && source.dockerfile === "Dockerfile", `${label} source mismatch`, source);
  assertCondition(build.willCompile === false, `${label} dry-run must not compile`, build);
  assertCondition(build.willRunDockerBuild === false, `${label} dry-run must not build Docker images`, build);
  assertCondition(build.willRunDockerComposeBuild === false, `${label} dry-run must not run docker compose build`, build);
  assertCondition(target.kind === "compose", `${label} target kind mismatch`, target);
  assertCondition(target.runtimeHost === "main-server", `${label} runtime host mismatch`, target);
  assertCondition(target.composeService === serviceId, `${label} compose service mismatch`, target);
  assertCondition(target.containerName === "todo-note-backend", `${label} container mismatch`, target);
  assertCondition(target.targetImage === "todo-note", `${label} target image mismatch`, target);
  assertCondition(target.runtimeImage === `todo-note:${commit}`, `${label} runtime image mismatch`, target);
  assertCondition(target.deployCommandShape === "docker compose up -d --no-build --no-deps --force-recreate todo-note", `${label} command shape mismatch`, target);
  assertCondition(labels["unidesk.ai/service-id"] === serviceId, `${label} service label mismatch`, labels);
  assertCondition(labels["unidesk.ai/source-repo"] === sourceRepo, `${label} source repo label mismatch`, labels);
  assertCondition(labels["unidesk.ai/source-commit"] === commit, `${label} source commit label mismatch`, labels);
  assertCondition(labels["unidesk.ai/dockerfile"] === "Dockerfile", `${label} dockerfile label mismatch`, labels);
  assertCondition(runtimeProof.kind === "compose-container-runtime-metadata", `${label} runtime proof kind mismatch`, runtimeProof);
  assertCondition(runtimeProof.sourceDirectoryUsed === false, `${label} runtime proof must not use source directory guesses`, runtimeProof);
  for (const expected of ["service-health", "container-env", "container-labels", "image-labels"]) {
    assertCondition(sources.includes(expected), `${label} runtime proof sources should include ${expected}`, runtimeProof);
  }
  for (const expected of [
    "UNIDESK_DEPLOY_COMMIT",
    "UNIDESK_DEPLOY_REQUESTED_COMMIT",
    "UNIDESK_TODO_NOTE_DEPLOY_COMMIT",
    "UNIDESK_TODO_NOTE_DEPLOY_REQUESTED_COMMIT",
  ]) {
    assertCondition(requiredEnvKeys.includes(expected), `${label} runtime proof env keys should include ${expected}`, runtimeProof);
  }
  assertCondition(validation.some((line) => line.includes("Compose container runtime metadata") && line.includes("not source directory guesses")), `${label} validation must name runtime metadata proof`, validation);
  assertCondition(!JSON.stringify(plan).includes("/root/todo_note"), `${label} dry-run must not rely on the todo-note source directory`, plan);
  assertCondition(!JSON.stringify(plan).includes("docker compose build"), `${label} dry-run must not mention docker compose build`, plan);
}

async function main(): Promise<void> {
  assertTodoNoteComposeYaml();
  assertPlan(runDeployApplyDryRun(), "deploy apply");
  const artifactPlan = asRecord(await runArtifactRegistryCommand([
    "deploy-service",
    "--env",
    "prod",
    "--service",
    serviceId,
    "--commit",
    commit,
    "--dry-run",
  ]), "artifact-registry dry-run");
  assertPlan(artifactPlan, "artifact-registry");

  process.stdout.write(`${JSON.stringify({
    ok: true,
    checks: [
      "todo-note Compose service names the stable artifact image and deploy labels",
      "deploy apply prod dry-run is a no-build/no-deps main-server Compose artifact consumer",
      "artifact-registry prod dry-run requires source repo/source commit/Dockerfile image labels",
      "runtime proof uses container env, container labels, image labels and health output",
      "runtime proof does not infer commit from /root/todo_note or any source directory",
    ],
  }, null, 2)}\n`);
}

if (import.meta.main) {
  await main();
}
