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

type JsonRecord = Record<string, unknown>;

const frontendCommit = "b5486a61ab0aa6c227366a95d1afa68281584359";
const frontendDigest = "sha256:76d7c47e797605470959ca2274f116149bdc367e6fa155913d19f42516e5b9e4";
const frontendRepo = "https://github.com/pikasTech/unidesk";
const frontendDockerfile = "src/components/frontend/Dockerfile";
const frontendRegistryRepository = "unidesk/frontend";
const frontendImageRef = `127.0.0.1:5000/${frontendRegistryRepository}:${frontendCommit}`;

const observedEvidence = {
  registry: {
    imageRef: frontendImageRef,
    contentType: "application/vnd.docker.distribution.manifest.v2+json",
    digest: frontendDigest,
  },
  health: {
    dev: {
      ok: true,
      service: "unidesk-frontend",
      deploy: {
        serviceId: "frontend",
        repo: frontendRepo,
        commit: frontendCommit,
        requestedCommit: frontendCommit,
      },
      environment: "dev",
      namespace: "unidesk-dev",
      databaseName: "unidesk_dev",
      serviceId: "frontend",
      deployRef: "origin/master:deploy.json#environments.dev.services.frontend",
    },
    prod: {
      ok: true,
      service: "unidesk-frontend",
      deploy: {
        serviceId: "frontend",
        repo: frontendRepo,
        commit: frontendCommit,
        requestedCommit: frontendCommit,
      },
    },
  },
  publishDryRun: {
    ok: true,
    mode: "dry-run-preflight",
    runnerDisposition: "ready",
    supportedArtifactPublish: true,
    serviceId: "frontend",
    commit: frontendCommit,
    missingControlChannels: [],
    controlChannels: [
      { channel: "backend-core", ok: true },
      { channel: "database", ok: true },
      { channel: "provider", ok: true },
      { channel: "registry", ok: true },
    ],
    registry: {
      runtimeApiHealthy: true,
      decision: "service-degraded",
      failedScopes: ["rendered-config", "registry-image"],
      healthyScopes: ["systemd", "docker", "registry-container", "loopback-listener", "registry-api", "storage"],
    },
    artifactSummary: {
      serviceId: "frontend",
      sourceCommit: frontendCommit,
      sourceRepo: frontendRepo,
      dockerfile: frontendDockerfile,
      registry: "127.0.0.1:5000",
      repository: "127.0.0.1:5000/unidesk/frontend",
      tag: frontendCommit,
      imageRef: frontendImageRef,
      digest: null,
      digestRef: null,
    },
    controlledPublish: {
      environment: "D601",
      namespace: "unidesk-ci",
      pipeline: "unidesk-user-service-artifact-publish",
      command: `bun scripts/cli.ts ci publish-user-service --service frontend --commit ${frontendCommit} --wait-ms 1200000`,
      requiresReadyControlChannels: ["backend-core", "database", "provider", "registry"],
    },
    boundary: "preflight is read-only: no D601 source export, no Tekton PipelineRun, no image push, no deploy apply, no service restart",
  },
} satisfies JsonRecord;

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

function manifestService(manifest: JsonRecord, environment: "dev" | "prod", serviceId: string): JsonRecord {
  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 service = services.map((item, index) => asRecord(item, `${environment}.services[${index}]`)).find((item) => item.id === serviceId);
  assertCondition(service !== undefined, `deploy.json ${environment} must include ${serviceId}`, env);
  return service as JsonRecord;
}

function assertDeployJson(): void {
  const manifest = asRecord(JSON.parse(readFileSync(rootPath("deploy.json"), "utf8")) as unknown, "deploy.json");
  assertCondition(manifest.schemaVersion === 2, "deploy.json must use schemaVersion=2", manifest);
  for (const environment of ["dev", "prod"] as const) {
    const service = manifestService(manifest, environment, "frontend");
    assertCondition(service.repo === frontendRepo, `${environment} frontend repo must stay on UniDesk`, service);
    assertCondition(service.commitId === frontendCommit, `${environment} frontend desired commit must match the reviewed artifact`, service);
  }
}

function assertCiCatalog(): void {
  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 frontend = artifacts.find((item) => item.serviceId === "frontend");
  assertCondition(frontend !== undefined, "CI.json must include frontend", catalog);
  const artifact = frontend as JsonRecord;
  const source = asRecord(artifact.source, "CI.json frontend.source");
  const image = asRecord(artifact.image, "CI.json frontend.image");
  assertCondition(artifact.kind === "source-build", "frontend CI producer must be source-build", artifact);
  assertCondition(artifact.status === "supported", "frontend CI producer must be supported", artifact);
  assertCondition(artifact.producer === "ci publish-user-service", "frontend CI producer must use publish-user-service", artifact);
  assertCondition(source.repo === frontendRepo, "frontend source repo mismatch", source);
  assertCondition(source.dockerfile === frontendDockerfile, "frontend Dockerfile mismatch", source);
  assertCondition(image.repository === frontendRegistryRepository, "frontend registry repository mismatch", image);
}

function assertObservedHealth(environment: "dev" | "prod"): void {
  const health = asRecord(asRecord(observedEvidence.health, "health evidence")[environment], `${environment} health`);
  const deploy = asRecord(health.deploy, `${environment} health.deploy`);
  assertCondition(health.ok === true, `${environment} health must be ok`, health);
  assertCondition(health.service === "unidesk-frontend", `${environment} health service mismatch`, health);
  assertCondition(deploy.serviceId === "frontend", `${environment} health deploy service id mismatch`, deploy);
  assertCondition(deploy.repo === frontendRepo, `${environment} health deploy repo mismatch`, deploy);
  assertCondition(deploy.commit === frontendCommit, `${environment} health deploy commit mismatch`, deploy);
  assertCondition(deploy.requestedCommit === frontendCommit, `${environment} health requested commit mismatch`, deploy);
  if (environment === "dev") {
    assertCondition(health.environment === "dev", "dev health must expose dev environment", health);
    assertCondition(health.namespace === "unidesk-dev", "dev health must expose unidesk-dev namespace", health);
    assertCondition(health.deployRef === "origin/master:deploy.json#environments.dev.services.frontend", "dev health deployRef mismatch", health);
  }
}

function assertRegistryDigest(): void {
  const registry = asRecord(observedEvidence.registry, "registry evidence");
  assertCondition(registry.imageRef === frontendImageRef, "registry image ref mismatch", registry);
  assertCondition(registry.contentType === "application/vnd.docker.distribution.manifest.v2+json", "registry digest must come from the v2 manifest", registry);
  assertCondition(registry.digest === frontendDigest, "registry digest mismatch", registry);
  assertCondition(/^sha256:[0-9a-f]{64}$/u.test(String(registry.digest)), "registry digest must be a sha256 manifest digest", registry);
}

function assertPublishDryRunReady(): void {
  const preflight = asRecord(observedEvidence.publishDryRun, "publish dry-run evidence");
  const missingControlChannels = stringArray(preflight.missingControlChannels, "publish missingControlChannels");
  const artifactSummary = asRecord(preflight.artifactSummary, "publish artifactSummary");
  const controlledPublish = asRecord(preflight.controlledPublish, "publish controlledPublish");
  const registry = asRecord(preflight.registry, "publish registry");
  const controlChannels = asArray(preflight.controlChannels, "publish controlChannels").map((item, index) => asRecord(item, `publish controlChannels[${index}]`));

  assertCondition(preflight.ok === true, "frontend publish dry-run must be ready", preflight);
  assertCondition(preflight.mode === "dry-run-preflight", "frontend publish dry-run mode mismatch", preflight);
  assertCondition(preflight.runnerDisposition === "ready", "frontend publish dry-run runnerDisposition mismatch", preflight);
  assertCondition(preflight.supportedArtifactPublish === true, "frontend publish must be supported", preflight);
  assertCondition(missingControlChannels.length === 0, "frontend publish dry-run must not miss control channels", preflight);
  for (const channel of ["backend-core", "database", "provider", "registry"]) {
    assertCondition(controlChannels.some((item) => item.channel === channel && item.ok === true), `publish dry-run ${channel} channel must be ready`, controlChannels);
  }
  assertCondition(registry.runtimeApiHealthy === true, "registry runtime API must be healthy for publish readiness", registry);
  assertCondition(stringArray(registry.healthyScopes, "publish registry.healthyScopes").includes("registry-api"), "registry-api scope must be healthy", registry);
  assertCondition(artifactSummary.imageRef === frontendImageRef, "publish artifact image must be commit-pinned", artifactSummary);
  assertCondition(artifactSummary.digest === null && artifactSummary.digestRef === null, "publish dry-run must not fake a digest", artifactSummary);
  assertCondition(controlledPublish.environment === "D601", "controlled publish environment mismatch", controlledPublish);
  assertCondition(controlledPublish.namespace === "unidesk-ci", "controlled publish namespace mismatch", controlledPublish);
  assertCondition(controlledPublish.pipeline === "unidesk-user-service-artifact-publish", "controlled publish pipeline mismatch", controlledPublish);
  assertCondition(stringArray(controlledPublish.requiresReadyControlChannels, "controlled publish channels").join(",") === "backend-core,database,provider,registry", "controlled publish required channel order mismatch", controlledPublish);
  assertCondition(String(preflight.boundary).includes("read-only"), "publish dry-run boundary must be read-only", preflight);
  assertCondition(!String(preflight.boundary).includes("service restart") || String(preflight.boundary).includes("no service restart"), "publish dry-run boundary must forbid restarts", preflight);
}

function assertNoRuntimeBuild(plan: JsonRecord, environment: "dev" | "prod"): void {
  const build = asRecord(plan.build, `${environment} build`);
  const registry = asRecord(plan.registry, `${environment} registry`);
  const source = asRecord(plan.source, `${environment} source`);
  const target = asRecord(plan.target, `${environment} target`);
  const validation = stringArray(plan.validation, `${environment} validation`);
  const labels = asRecord(plan.requiredLabels, `${environment} labels`);
  const registryProbe = asRecord(plan.registryProbe, `${environment} registryProbe`);

  assertCondition(plan.ok === true && plan.supported === true, `${environment} frontend dry-run must be supported`, plan);
  assertCondition(plan.dryRun === true && plan.mutation === false, `${environment} frontend dry-run must be non-mutating`, plan);
  assertCondition(plan.environment === environment, `${environment} dry-run environment mismatch`, plan);
  assertCondition(plan.providerId === "D601", `${environment} dry-run provider mismatch`, plan);
  assertCondition(plan.serviceId === "frontend", `${environment} dry-run service id mismatch`, plan);
  assertCondition(plan.commit === frontendCommit, `${environment} dry-run commit mismatch`, plan);
  assertCondition(plan.sourceImage === frontendImageRef, `${environment} dry-run source image mismatch`, plan);
  assertCondition(source.repo === frontendRepo && source.commit === frontendCommit && source.dockerfile === frontendDockerfile, `${environment} source boundary mismatch`, source);
  assertCondition(registry.imageRef === frontendImageRef, `${environment} registry image ref mismatch`, registry);
  assertCondition(registry.digest === null, `${environment} dry-run must not fake registry digest`, registry);
  assertCondition(String(registry.digestSource ?? "").includes("live apply must read this digest"), `${environment} digest source must name live registry HEAD`, registry);
  assertCondition(registryProbe.method === "HEAD", `${environment} registry probe must be HEAD-only`, registryProbe);
  assertCondition(labels["unidesk.ai/service-id"] === "frontend", `${environment} service label mismatch`, labels);
  assertCondition(labels["unidesk.ai/source-commit"] === frontendCommit, `${environment} source commit label mismatch`, labels);
  assertCondition(labels["unidesk.ai/dockerfile"] === frontendDockerfile, `${environment} Dockerfile label mismatch`, labels);
  assertCondition(build.willCompile === false, `${environment} CD must not compile`, build);
  assertCondition(build.willRunCargoBuild === false, `${environment} CD must not run cargo build`, build);
  assertCondition(build.willRunDockerBuild === false, `${environment} CD must not run docker build`, build);
  assertCondition(build.willRunDockerComposeBuild === false, `${environment} CD must not run docker compose build`, build);
  assertCondition(build.producerBoundary === "ci publish-user-service", `${environment} producer boundary mismatch`, build);
  assertCondition(String(plan.boundary ?? "").includes("artifact-consumer only"), `${environment} boundary must say artifact-consumer only`, plan);
  assertCondition(String(plan.boundary ?? "").includes("never builds source"), `${environment} boundary must forbid runtime source builds`, plan);

  if (environment === "dev") {
    assertCondition(target.kind === "d601-k3s", "dev frontend target must be D601 k3s", target);
    assertCondition(target.namespace === "unidesk-dev", "dev frontend namespace mismatch", target);
    assertCondition(target.deployment === "frontend-dev", "dev frontend deployment mismatch", target);
    assertCondition(target.service === "frontend-dev", "dev frontend service mismatch", target);
    assertCondition(target.runtimeImage === `unidesk-frontend:${frontendCommit}`, "dev frontend runtime image mismatch", target);
    assertCondition(validation.some((line) => line.includes("Kubernetes API service proxy")), "dev validation must use Kubernetes API service proxy", validation);
  } else {
    assertCondition(target.kind === "compose", "prod frontend target must be Compose", target);
    assertCondition(target.runtimeHost === "main-server", "prod frontend runtime host mismatch", target);
    assertCondition(target.composeService === "frontend", "prod frontend Compose service mismatch", target);
    assertCondition(target.containerName === "unidesk-frontend", "prod frontend container mismatch", target);
    assertCondition(target.deployCommandShape === "docker compose up -d --no-build --no-deps --force-recreate frontend", "prod frontend deploy command shape mismatch", target);
    assertCondition(validation.some((line) => line.includes("deploy.commit/deploy.requestedCommit")), "prod validation must require health commit metadata", validation);
  }
}

async function main(): Promise<void> {
  assertDeployJson();
  assertCiCatalog();
  assertRegistryDigest();
  assertObservedHealth("dev");
  assertObservedHealth("prod");
  assertPublishDryRunReady();

  const devDryRun = asRecord(await runArtifactRegistryCommand([
    "deploy-service",
    "--env",
    "dev",
    "--service",
    "frontend",
    "--commit",
    frontendCommit,
    "--dry-run",
  ]), "dev artifact dry-run");
  const prodDryRun = asRecord(await runArtifactRegistryCommand([
    "deploy-service",
    "--env",
    "prod",
    "--service",
    "frontend",
    "--commit",
    frontendCommit,
    "--dry-run",
  ]), "prod artifact dry-run");

  assertNoRuntimeBuild(devDryRun, "dev");
  assertNoRuntimeBuild(prodDryRun, "prod");

  process.stdout.write(`${JSON.stringify({
    ok: true,
    checks: [
      "deploy.json dev/prod desired frontend commit matches the reviewed artifact",
      "CI.json frontend producer remains ci publish-user-service source-build",
      "registry v2 manifest digest is pinned as contract evidence",
      "dev and prod health report matching deploy.commit/requestedCommit",
      "publish-user-service dry-run is ready and read-only",
      "dev CD dry-run is D601 k3s artifact-only and non-mutating",
      "prod CD dry-run is main-server Compose artifact-only and non-mutating",
    ],
    frontend: {
      commit: frontendCommit,
      artifact: frontendImageRef,
      digest: frontendDigest,
      devHealthCommit: frontendCommit,
      prodHealthCommit: frontendCommit,
      uiAcceptanceIndependentOfCiCd: true,
    },
    dryRunTargets: {
      dev: asRecord(devDryRun.target, "dev target"),
      prod: asRecord(prodDryRun.target, "prod target"),
    },
  }, null, 2)}\n`);
}

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