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

type JsonRecord = Record<string, unknown>;

const serviceId = "code-queue-mgr";
const repo = "https://github.com/pikasTech/unidesk";
const commit = "fee1b1b710151d827749cc4b0662b1560cbe1fd6";
const dockerfile = "src/components/microservices/code-queue-mgr/Dockerfile";
const repository = "unidesk/code-queue-mgr";
const imageRef = `127.0.0.1:5000/${repository}:${commit}`;

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 strings(value: unknown, label: string): string[] {
  return asArray(value, label).map(String);
}

function manifestService(environment: "dev" | "prod", id: string): JsonRecord {
  const manifest = asRecord(JSON.parse(readFileSync(rootPath("deploy.json"), "utf8")) as unknown, "deploy.json");
  const environments = asRecord(manifest.environments, "deploy.json.environments");
  const env = asRecord(environments[environment], `deploy.json.environments.${environment}`);
  const services = asArray(env.services, `deploy.json.environments.${environment}.services`);
  const found = services.map((item, index) => asRecord(item, `${environment}.services[${index}]`)).find((item) => item.id === id);
  assertCondition(found !== undefined, `deploy.json ${environment} must include ${id}`, env);
  return found as JsonRecord;
}

function ciArtifact(id: string): JsonRecord {
  const catalog = asRecord(JSON.parse(readFileSync(rootPath("CI.json"), "utf8")) as unknown, "CI.json");
  const artifacts = asArray(catalog.artifacts, "CI.json.artifacts").map((item, index) => asRecord(item, `CI.json.artifacts[${index}]`));
  const found = artifacts.find((item) => item.serviceId === id);
  assertCondition(found !== undefined, `CI.json must include ${id}`, catalog);
  return found as JsonRecord;
}

function assertDesiredStateAndProducer(): void {
  const prod = manifestService("prod", serviceId);
  const dev = manifestService("dev", serviceId);
  const artifact = ciArtifact(serviceId);
  const source = asRecord(artifact.source, "CI.json code-queue-mgr.source");
  const image = asRecord(artifact.image, "CI.json code-queue-mgr.image");

  assertCondition(prod.repo === repo, "prod code-queue-mgr repo mismatch", prod);
  assertCondition(prod.commitId === commit, "prod code-queue-mgr must stay pinned to the stats endpoint commit", prod);
  assertCondition(dev.repo === repo, "dev code-queue-mgr repo mismatch", dev);
  assertCondition(artifact.kind === "source-build", "code-queue-mgr artifact must be source-build", artifact);
  assertCondition(artifact.status === "supported", "code-queue-mgr artifact publish must be supported", artifact);
  assertCondition(artifact.producer === "ci publish-user-service", "code-queue-mgr must publish through user-service artifact CI", artifact);
  assertCondition(source.repo === repo, "code-queue-mgr CI source repo mismatch", source);
  assertCondition(source.dockerfile === dockerfile, "code-queue-mgr CI Dockerfile mismatch", source);
  assertCondition(image.repository === repository, "code-queue-mgr registry repository mismatch", image);
}

function assertStatsEndpointSourceContract(): void {
  const dockerfileText = readFileSync(rootPath(dockerfile), "utf8");
  const rustSource = readFileSync(rootPath("src/components/microservices/code-queue-mgr/src-rs/main.rs"), "utf8");
  const statsRouteIndex = rustSource.indexOf('(Method::Get, "/api/tasks/stats")');
  const statsRouteSnippet = statsRouteIndex >= 0 ? rustSource.slice(statsRouteIndex, statsRouteIndex + 900) : "";

  assertCondition(dockerfileText.includes("COPY src/components/microservices/code-queue-mgr/src-rs ./src-rs"), "code-queue-mgr artifact must build the Rust runtime source", { dockerfile });
  assertCondition(dockerfileText.includes('CMD ["code-queue-mgr"]'), "code-queue-mgr artifact must run the Rust binary", { dockerfile });
  assertCondition(statsRouteIndex >= 0, "Rust mgr must expose /api/tasks/stats", { path: "src-rs/main.rs" });
  assertCondition(statsRouteSnippet.includes("task_statistics_summary"), "Rust mgr stats route must use task_statistics_summary", { statsRouteSnippet });
  assertCondition(rustSource.includes("stats_summary_contract_exposes_daily_buckets_and_totals"), "Rust mgr must keep the daily bucket stats unit contract", { path: "src-rs/main.rs" });
  assertCondition(!statsRouteSnippet.includes("skipped"), "Rust mgr stats route must not ship skipped statistics", { statsRouteSnippet });
}

function assertCommonDryRun(plan: JsonRecord, deployRef: string): void {
  const source = asRecord(plan.source, "dry-run source");
  const registry = asRecord(plan.registry, "dry-run registry");
  const build = asRecord(plan.build, "dry-run build");
  const labels = asRecord(plan.requiredLabels, "dry-run labels");
  const probe = asRecord(plan.registryProbe, "dry-run registryProbe");
  const target = asRecord(plan.target, "dry-run target");
  const liveApply = asRecord(plan.liveApply, "dry-run liveApply");
  const guard = asRecord(plan.selfBootstrapGuard, "dry-run selfBootstrapGuard");
  const validation = strings(plan.validation, "dry-run validation");
  const excludedTargets = asArray(plan.excludedTargets, "dry-run excludedTargets").map((item, index) => asRecord(item, `excludedTargets[${index}]`));
  const excludedText = JSON.stringify(excludedTargets);

  assertCondition(plan.ok === true && plan.supported === true, "code-queue-mgr dry-run must be supported", plan);
  assertCondition(plan.dryRun === true && plan.mutation === false, "code-queue-mgr dry-run must be non-mutating", plan);
  assertCondition(plan.environment === "prod", "code-queue-mgr dry-run must target prod", plan);
  assertCondition(plan.providerId === "D601", "code-queue-mgr dry-run provider mismatch", plan);
  assertCondition(plan.serviceId === serviceId, "code-queue-mgr dry-run service mismatch", plan);
  assertCondition(plan.commit === commit, "code-queue-mgr dry-run commit mismatch", plan);
  assertCondition(plan.sourceRepo === repo, "code-queue-mgr dry-run source repo mismatch", plan);
  assertCondition(plan.deployRef === deployRef, "code-queue-mgr dry-run deployRef mismatch", plan);
  assertCondition(plan.sourceImage === imageRef, "code-queue-mgr dry-run source image mismatch", plan);

  assertCondition(source.repo === repo && source.commit === commit && source.dockerfile === dockerfile, "code-queue-mgr source boundary mismatch", source);
  assertCondition(registry.imageRef === imageRef, "code-queue-mgr registry image mismatch", registry);
  assertCondition(registry.digest === null, "code-queue-mgr dry-run must not fake registry digest", registry);
  assertCondition(String(registry.digestSource ?? "").includes("live apply must read this digest"), "code-queue-mgr digest source must name live registry HEAD", registry);
  assertCondition(probe.method === "HEAD", "code-queue-mgr registry probe must be HEAD-only", probe);

  assertCondition(build.willCompile === false, "code-queue-mgr CD must not compile", build);
  assertCondition(build.willRunCargoBuild === false, "code-queue-mgr CD must not run cargo build", build);
  assertCondition(build.willRunDockerBuild === false, "code-queue-mgr CD must not run docker build", build);
  assertCondition(build.willRunDockerComposeBuild === false, "code-queue-mgr CD must not run docker compose build", build);
  assertCondition(build.producerBoundary === "ci publish-user-service", "code-queue-mgr producer boundary mismatch", build);

  assertCondition(labels["unidesk.ai/service-id"] === serviceId, "code-queue-mgr label service mismatch", labels);
  assertCondition(labels["unidesk.ai/source-repo"] === repo, "code-queue-mgr label repo mismatch", labels);
  assertCondition(labels["unidesk.ai/source-commit"] === commit, "code-queue-mgr label commit mismatch", labels);
  assertCondition(labels["unidesk.ai/dockerfile"] === dockerfile, "code-queue-mgr label Dockerfile mismatch", labels);

  assertCondition(target.kind === "compose", "code-queue-mgr target must be main-server Compose", target);
  assertCondition(target.runtimeHost === "main-server", "code-queue-mgr runtime host mismatch", target);
  assertCondition(target.composeService === "code-queue-mgr", "code-queue-mgr Compose service mismatch", target);
  assertCondition(target.containerName === "code-queue-mgr-backend", "code-queue-mgr container mismatch", target);
  assertCondition(target.targetImage === "code-queue-mgr", "code-queue-mgr target image mismatch", target);
  assertCondition(target.runtimeImage === `code-queue-mgr:${commit}`, "code-queue-mgr runtime image mismatch", target);
  assertCondition(target.deployEnvPrefix === "UNIDESK_CODE_QUEUE_MGR_DEPLOY", "code-queue-mgr deploy env prefix mismatch", target);
  assertCondition(target.deployCommandShape === "docker compose up -d --no-build --no-deps --force-recreate code-queue-mgr", "code-queue-mgr deploy command must be single-service no-build/no-deps recreate", target);

  assertCondition(liveApply.policy === "supervisor-only", "code-queue-mgr prod live apply must be supervisor-only", liveApply);
  assertCondition(liveApply.allowed === false, "code-queue-mgr prod live apply must be blocked in automation", liveApply);
  assertCondition(liveApply.requiresSupervisorApproval === true, "code-queue-mgr prod live apply must require supervisor approval", liveApply);
  assertCondition(String(liveApply.reason ?? "").includes("explicit supervisor confirmation"), "code-queue-mgr live apply reason must name supervisor confirmation", liveApply);
  assertCondition(plan.requiresSupervisorApproval === true, "code-queue-mgr prod dry-run must expose top-level supervisor approval requirement", plan);
  assertCondition(guard.selfBootstrapBlocked === true, "code-queue-mgr prod dry-run must expose self-bootstrap guard", guard);
  assertCondition(String(guard.targetScope ?? "").includes("code-queue-mgr-backend"), "code-queue-mgr guard must name the control-plane sidecar target", guard);
  assertCondition(validation.some((line) => line.includes("deploy.commit/deploy.requestedCommit")), "code-queue-mgr validation must require health deploy commit metadata", validation);
  assertCondition(excludedText.includes("code-queue"), "code-queue-mgr excluded targets must include code-queue", excludedTargets);
  assertCondition(excludedText.includes("scheduler"), "code-queue-mgr excluded targets must mention scheduler", excludedTargets);
  assertCondition(excludedText.includes("runner"), "code-queue-mgr excluded targets must mention runner", excludedTargets);
  assertCondition(excludedText.includes("tasks"), "code-queue-mgr excluded targets must mention tasks", excludedTargets);
  assertCondition(excludedText.includes("interrupts"), "code-queue-mgr excluded targets must mention interrupts", excludedTargets);
  assertCondition(excludedText.includes("cancellations"), "code-queue-mgr excluded targets must mention cancellations", excludedTargets);
  assertCondition(!JSON.stringify(plan).includes("server rebuild"), "code-queue-mgr dry-run must not mention server rebuild", plan);
  assertCondition(!JSON.stringify(plan).includes("docker compose build"), "code-queue-mgr dry-run must not mention compose build", plan);
}

function deployApplyDryRun(): 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 for prod code-queue-mgr", {
    status: result.status,
    stdoutTail: result.stdout.slice(-2000),
    stderrTail: result.stderr.slice(-2000),
  });
  const envelope = asRecord(JSON.parse(result.stdout) as unknown, "deploy apply envelope");
  assertCondition(envelope.ok === true, "deploy apply envelope must be ok", envelope);
  const data = asRecord(envelope.data, "deploy apply data");
  const results = asArray(data.results, "deploy apply results");
  assertCondition(data.action === "apply", "deploy apply dry-run action mismatch", data);
  assertCondition(data.environment === "prod", "deploy apply dry-run environment mismatch", data);
  assertCondition(data.executor === "d601-registry-artifact-consumer", "deploy apply dry-run executor mismatch", data);
  assertCondition(data.dryRun === true, "deploy apply dry-run must report dryRun=true", data);
  assertCondition(results.length === 1, "deploy apply dry-run must return exactly one service result", data);
  return asRecord(results[0], "deploy apply code-queue-mgr result");
}

async function main(): Promise<void> {
  assertDesiredStateAndProducer();
  assertStatsEndpointSourceContract();

  const artifactDryRun = asRecord(await runArtifactRegistryCommand([
    "deploy-service",
    "--env",
    "prod",
    "--service",
    serviceId,
    "--commit",
    commit,
    "--dry-run",
  ]), "artifact-registry dry-run");
  assertCommonDryRun(artifactDryRun, "deploy.json#environments.prod.services.code-queue-mgr");

  const deployDryRun = deployApplyDryRun();
  assertCommonDryRun(deployDryRun, "origin/master:deploy.json#environments.prod.services.code-queue-mgr");

  process.stdout.write(`${JSON.stringify({
    ok: true,
    checks: [
      "prod deploy.json pins code-queue-mgr to the stats endpoint commit",
      "CI.json publishes code-queue-mgr through ci publish-user-service",
      "Dockerfile builds and runs the Rust mgr that exposes /api/tasks/stats without skipped=true",
      "artifact-registry prod dry-run is non-mutating and single-service Compose only",
      "deploy apply prod dry-run is non-mutating, supervisor-only, and excludes scheduler/runner/tasks/interrupt/cancel",
    ],
    serviceId,
    commit,
    artifact: imageRef,
    target: asRecord(deployDryRun.target, "deploy dry-run target"),
    liveApply: asRecord(deployDryRun.liveApply, "deploy dry-run liveApply"),
  }, null, 2)}\n`);
}

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