import { readFileSync } from "node:fs";
import {
  compareDeployJsonExecutorMirrors,
  deployJsonDriftResult,
  deployJsonSourceOfTruth,
  encodeDeployJsonServiceContract,
  k3sManifestExecutorMirror,
  parseDeployJsonServiceContract,
  type DeployJsonServiceContract,
  type DeployJsonExecutorMirror,
} from "./src/deploy-json-contract";
import { rootPath } from "./src/config";
import { runArtifactRegistryCommand } from "./src/artifact-registry";

type JsonRecord = Record<string, unknown>;

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[];
}

interface ContractCase {
  serviceId: string;
  expectedRuntimeImage: string;
  driftPort: number;
}

function deployService(environment: "dev" | "prod", serviceId: string): DeployJsonServiceContract {
  const deploy = asRecord(JSON.parse(readFileSync(rootPath("deploy.json"), "utf8")) as unknown, "deploy.json");
  const environments = asRecord(deploy.environments, "deploy.json.environments");
  const env = asRecord(environments[environment], `deploy.json.environments.${environment}`);
  const services = asArray(env.services, `deploy.json.environments.${environment}.services`);
  const raw = services.find((item) => asRecord(item, `${environment} service`).id === serviceId);
  assertCondition(raw !== undefined, `deploy.json must contain ${environment}/${serviceId}`);
  return parseDeployJsonServiceContract(raw, `deploy.json.environments.${environment}.services.${serviceId}`);
}

const deploySource = readFileSync(rootPath("scripts/src/deploy.ts"), "utf8");
assertCondition(deploySource.includes('"--deploy-json-service", encodeDeployJsonServiceContract(service)'), "deploy executor must pass deploy.json service contract into artifact-registry", {});

const cases: ContractCase[] = [
  {
    serviceId: "mdtodo",
    expectedRuntimeImage: "unidesk-mdtodo",
    driftPort: 4268,
  },
  {
    serviceId: "decision-center",
    expectedRuntimeImage: "unidesk-decision-center",
    driftPort: 4278,
  },
];

const verifiedServices: string[] = [];
for (const item of cases) {
  const service = deployService("dev", item.serviceId);
  const artifact = asRecord(service.artifact, `${item.serviceId} artifact`);
  const consumer = asRecord(service.consumer, `${item.serviceId} consumer`);
  const targetContract = asRecord(consumer.target, `${item.serviceId} consumer target`);
  const runtime = asRecord(service.runtime, `${item.serviceId} runtime`);
  const memory = asRecord(runtime.memory, `${item.serviceId} runtime memory`);
  const health = asRecord(runtime.health, `${item.serviceId} runtime health`);
  const commit = String(service.commitId);
  const sourceOfTruth = deployJsonSourceOfTruth(service, "dev");

  const applyPlan = asRecord(await runArtifactRegistryCommand([
    "deploy-service",
    "--env",
    "dev",
    "--service",
    item.serviceId,
    "--commit",
    commit,
    "--source-repo",
    service.repo,
    "--deploy-ref",
    `origin/master:deploy.json#environments.dev.services.${item.serviceId}`,
    "--deploy-json-service",
    encodeDeployJsonServiceContract(service),
    "--dry-run",
  ]), `${item.serviceId} artifact-registry dry-run result`);
  const applyTarget = asRecord(applyPlan.target, `${item.serviceId} artifact-registry target`);
  const applyRegistry = asRecord(applyPlan.registry, `${item.serviceId} artifact-registry registry`);
  const applyRuntime = asRecord(applyPlan.runtime, `${item.serviceId} artifact-registry runtime`);
  const applyRuntimeMemory = asRecord(applyRuntime.memory, `${item.serviceId} artifact-registry runtime memory`);
  const applyRuntimeHealth = asRecord(applyRuntime.health, `${item.serviceId} artifact-registry runtime health`);
  const applyDriftCheck = asRecord(applyPlan.driftCheck, `${item.serviceId} artifact-registry driftCheck`);
  assertCondition(applyPlan.sourceOfTruth !== undefined, `${item.serviceId} artifact-registry dry-run should expose deploy.json sourceOfTruth`, applyPlan);
  assertCondition(JSON.stringify(applyPlan.sourceOfTruth) === JSON.stringify(sourceOfTruth), `${item.serviceId} artifact-registry sourceOfTruth must enumerate deploy.json fields`, applyPlan.sourceOfTruth);
  assertCondition(applyDriftCheck.ok === true, `${item.serviceId} artifact-registry dry-run must report a passing drift preflight`, applyDriftCheck);
  assertCondition(applyRegistry.repository === artifact.repository, `${item.serviceId} artifact-registry dry-run repository must come from deploy.json`, applyRegistry);
  assertCondition(applyRegistry.tag === commit, `${item.serviceId} artifact-registry dry-run tag must be deploy.json commitId`, applyRegistry);
  assertCondition(applyRegistry.imageRef === `127.0.0.1:5000/${artifact.repository}:${commit}`, `${item.serviceId} artifact-registry imageRef must be deploy.json artifact repository + commit`, applyRegistry);
  assertCondition(applyTarget.namespace === targetContract.namespace, `${item.serviceId} artifact-registry dry-run namespace must come from deploy.json`, applyTarget);
  assertCondition(applyTarget.deployment === targetContract.deployment, `${item.serviceId} artifact-registry dry-run deployment must come from deploy.json`, applyTarget);
  assertCondition(applyTarget.service === targetContract.service, `${item.serviceId} artifact-registry dry-run service must come from deploy.json`, applyTarget);
  assertCondition(asArray(applyTarget.deployments, `${item.serviceId} artifact-registry deployments`).some((deployment) => asRecord(deployment, "deployment").name === targetContract.deployment), `${item.serviceId} artifact-registry deployments must come from deploy.json target`, applyTarget);
  assertCondition(applyTarget.stableImage === targetContract.stableImage, `${item.serviceId} artifact-registry stableImage must come from deploy.json`, applyTarget);
  assertCondition(applyTarget.runtimeImage === `${item.expectedRuntimeImage}:${commit}`, `${item.serviceId} artifact-registry runtimeImage must derive from deploy.json stableImage + commit`, applyTarget);
  assertCondition(applyTarget.manifestRepoPath === targetContract.manifestRepoPath, `${item.serviceId} artifact-registry manifest path must come from deploy.json`, applyTarget);
  assertCondition(applyRuntime.sourceOfTruth === "deploy.json", `${item.serviceId} artifact-registry runtime source must be deploy.json`, applyRuntime);
  assertCondition(applyRuntime.containerPort === runtime.containerPort, `${item.serviceId} artifact-registry runtime port must come from deploy.json`, applyRuntime);
  assertCondition(applyRuntime.healthPath === runtime.healthPath, `${item.serviceId} artifact-registry runtime healthPath must come from deploy.json`, applyRuntime);
  assertCondition(applyRuntimeMemory.request === memory.request && applyRuntimeMemory.limit === memory.limit, `${item.serviceId} artifact-registry memory must come from deploy.json`, applyRuntimeMemory);
  assertCondition(applyRuntimeHealth.deployMetadataRequired === health.deployMetadataRequired, `${item.serviceId} artifact-registry deploy metadata requirement must come from deploy.json`, applyRuntimeHealth);

  const implicitPlan = asRecord(await runArtifactRegistryCommand([
    "deploy-service",
    "--env",
    "dev",
    "--service",
    item.serviceId,
    "--commit",
    commit,
    "--dry-run",
  ]), `${item.serviceId} implicit deploy.json dry-run result`);
  assertCondition(asRecord(implicitPlan.registry, `${item.serviceId} implicit registry`).repository === artifact.repository, `${item.serviceId} implicit artifact-registry dry-run must read deploy.json from file`, implicitPlan);
  assertCondition(asRecord(implicitPlan.driftCheck, `${item.serviceId} implicit driftCheck`).ok === true, `${item.serviceId} implicit artifact-registry dry-run must drift-check deploy.json`, implicitPlan);

  const manifestMirror = k3sManifestExecutorMirror(service);
  assertCondition(manifestMirror !== null, `${item.serviceId} deploy.json contract should locate a k8s manifest mirror`);
  const cleanDrifts = compareDeployJsonExecutorMirrors(service, "dev", [manifestMirror!]);
  assertCondition(cleanDrifts.length === 0, `${item.serviceId} current k8s manifest mirror should match deploy.json contract`, cleanDrifts);

  const driftMirror: DeployJsonExecutorMirror = {
    ...manifestMirror!,
    runtime: {
      ...manifestMirror!.runtime,
      containerPort: item.driftPort,
      memory: {
        ...manifestMirror!.runtime?.memory,
        limit: "384Mi",
      },
      health: {
        ...manifestMirror!.runtime?.health,
        deployMetadataRequired: false,
      },
    },
  };
  const drift = compareDeployJsonExecutorMirrors(service, "dev", [driftMirror]);
  const driftResult = deployJsonDriftResult(service, "dev", drift);
  const driftPayload = asRecord(driftResult.drift, `${item.serviceId} drift result payload`);
  const driftItems = asArray(driftPayload.items, `${item.serviceId} drift result items`).map((entry) => asRecord(entry, "drift item"));
  assertCondition(driftResult.ok === false, `${item.serviceId} drift result must be non-ok`, driftResult);
  assertCondition(driftResult.error === "deploy-json-drift", `${item.serviceId} drift result should use structured deploy-json-drift error`, driftResult);
  assertCondition(driftItems.some((entry) => entry.field === "runtime.containerPort" && entry.expected === runtime.containerPort && entry.actual === item.driftPort), `${item.serviceId} drift result should report port mismatch`, driftItems);
  assertCondition(driftItems.some((entry) => entry.field === "runtime.memory.limit" && entry.expected === "512Mi" && entry.actual === "384Mi"), `${item.serviceId} drift result should report memory mismatch`, driftItems);
  assertCondition(driftItems.some((entry) => entry.field === "runtime.health.deployMetadataRequired" && entry.expected === true && entry.actual === false), `${item.serviceId} drift result should report deploy metadata requirement mismatch`, driftItems);
  verifiedServices.push(item.serviceId);
}

process.stdout.write(`${JSON.stringify({
  ok: true,
  checks: [
    "deploy executor passes the deploy.json service contract to artifact-registry dry-run",
    "artifact-registry dry-run reads k3s user-service image, target, port, memory and deploy metadata fields from deploy.json",
    "k8s manifest target/port/memory/deploy metadata are treated as derived mirrors and checked for drift",
    "drift preflight returns structured deploy-json-drift with field-level expected/actual values",
  ],
  services: verifiedServices,
}, null, 2)}\n`);
