#!/usr/bin/env bun
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { DEV_DB_ENV_CONTRACT } from "../internal/cloud/db-contract.ts";
import { DEV_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../internal/protocol/index.mjs";

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const serviceIdLabel = "hwlab.pikastech.local/service-id";
const namespace = "hwlab-dev";
const forbiddenRuntimeSubstitutes = Object.freeze([
  "unidesk-backend",
  "provider-gateway",
  "microservice-proxy"
]);

async function readText(relativePath) {
  return readFile(path.join(repoRoot, relativePath), "utf8");
}

async function readJSON(relativePath) {
  const raw = await readText(relativePath);
  return JSON.parse(raw);
}

function assertObject(value, label) {
  assert.ok(value && typeof value === "object" && !Array.isArray(value), `${label} must be an object`);
}

function assertArray(value, label) {
  assert.ok(Array.isArray(value), `${label} must be an array`);
}

function assertUnique(values, label) {
  assert.equal(new Set(values).size, values.length, `${label} must be unique`);
}

function assertSameMembers(actual, expected, label) {
  assert.deepEqual([...actual].sort(), [...expected].sort(), `${label} must match expected members`);
}

function assertServiceId(serviceId, label) {
  assert.ok(SERVICE_IDS.includes(serviceId), `${label} uses unknown serviceId ${JSON.stringify(serviceId)}`);
  assert.match(serviceId, /^hwlab-/, `${label} must be an HWLAB service id`);
  assertNoForbiddenSubstitute(serviceId, label);
}

function assertDevOnlyEnvironment(value, label) {
  assert.equal(value, ENVIRONMENT_DEV, `${label} must be dev`);
}

function assertDevNamespace(value, label) {
  assert.equal(value, namespace, `${label} must be ${namespace}`);
}

function assertForbiddenList(actual, label) {
  assertArray(actual, label);
  assertSameMembers(actual, forbiddenRuntimeSubstitutes, label);
}

function splitCsv(value) {
  assert.equal(typeof value, "string", "CSV value must be a string");
  return value
    .split(",")
    .map((item) => item.trim())
    .filter(Boolean);
}

function assertNoForbiddenSubstitute(value, label) {
  if (typeof value !== "string") {
    return;
  }
  const lower = value.toLowerCase();
  for (const forbidden of forbiddenRuntimeSubstitutes) {
    assert.equal(
      lower.includes(forbidden),
      false,
      `${label} must not use forbidden runtime substitute ${forbidden}`
    );
  }
}

function assertNoForbiddenSubstitutesDeep(value, label) {
  if (
    label.endsWith(".HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN") ||
    label.endsWith(".forbiddenRuntimeSubstitutes")
  ) {
    return;
  }

  if (typeof value === "string") {
    assertNoForbiddenSubstitute(value, label);
    return;
  }

  if (Array.isArray(value)) {
    value.forEach((item, index) => assertNoForbiddenSubstitutesDeep(item, `${label}[${index}]`));
    return;
  }

  if (value && typeof value === "object") {
    if (value.name === "HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN") {
      return;
    }

    for (const [key, item] of Object.entries(value)) {
      assertNoForbiddenSubstitutesDeep(item, `${label}.${key}`);
    }
  }
}

function containerList(workload, label) {
  const template = workload.kind === "Job" ? workload.spec?.template : workload.spec?.template;
  const containers = template?.spec?.containers;
  assertArray(containers, `${label}.containers`);
  return containers;
}

function labelsFor(resource, label) {
  const labels = resource.metadata?.labels;
  assertObject(labels, `${label}.metadata.labels`);
  return labels;
}

function assertMetadataServiceId(resource, expectedServiceId, label) {
  if (!resource.metadata?.labels?.["hwlab.pikastech.local/instance-id"]) {
    assert.equal(resource.metadata?.name, expectedServiceId, `${label}.metadata.name must match service id`);
  } else {
    assert.ok(resource.metadata?.name.startsWith(`${expectedServiceId}-`), `${label}.metadata.name must include service id prefix`);
  }
  assertDevNamespace(resource.metadata?.namespace, `${label}.metadata.namespace`);
  const labels = labelsFor(resource, label);
  assert.equal(labels[serviceIdLabel], expectedServiceId, `${label} service-id label`);
  assert.equal(labels["app.kubernetes.io/name"], expectedServiceId, `${label} app name label`);
}

function assertTemplateServiceId(workload, expectedServiceId, label) {
  const templateLabels = workload.spec?.template?.metadata?.labels;
  assertObject(templateLabels, `${label}.spec.template.metadata.labels`);
  assert.equal(templateLabels[serviceIdLabel], expectedServiceId, `${label} template service-id label`);
  assert.equal(templateLabels["app.kubernetes.io/name"], expectedServiceId, `${label} template app name label`);
}

function assertHwlabImage(image, serviceId, label) {
  assert.equal(
    typeof image,
    "string",
    `${label}.image must be a string`
  );
  assert.match(
    image,
    new RegExp(`^(ghcr\\.io/pikastech|127\\.0\\.0\\.1:5000/hwlab|localhost:5000/hwlab)/${serviceId}:[a-f0-9]{7,40}$`),
    `${label}.image must be the HWLAB image for ${serviceId}`
  );
}

function assertHealthProbe(container, serviceId, label) {
  for (const probeName of ["readinessProbe", "livenessProbe"]) {
    assert.equal(
      container[probeName]?.httpGet?.path,
      "/health/live",
      `${label}.${probeName} must use /health/live`
    );
  }
}

function assertNoExternalNameSubstitute(service, label) {
  assert.notEqual(service.spec?.type, "ExternalName", `${label} must not be an ExternalName substitute`);
  assert.equal(service.spec?.type, "ClusterIP", `${label} must be a ClusterIP skeleton service`);
}

function assertKustomizationDevOnly(kustomization, label) {
  assert.equal(kustomization.kind, "Kustomization", `${label}.kind`);
  assertDevNamespace(kustomization.namespace, `${label}.namespace`);
  assert.equal(kustomization.commonLabels?.["hwlab.pikastech.local/profile"], "dev", `${label} profile label`);
  assert.ok(kustomization.resources?.includes("../base"), `${label} must include ../base`);
}

function assertRuntimePolicyText(value, label) {
  assert.equal(typeof value, "string", `${label} must be a string`);
  assert.ok(value.includes("HWLAB runtime"), `${label} must name HWLAB runtime`);
  assert.ok(value.includes("UniDesk backend"), `${label} must name UniDesk backend`);
  assert.ok(value.includes("provider-gateway"), `${label} must name provider-gateway`);
  assert.ok(value.includes("microservice proxy"), `${label} must name microservice proxy`);
}

function assertIncludes(value, expected, label) {
  assert.equal(typeof value, "string", `${label} must be a string`);
  assert.ok(value.includes(expected), `${label} must include ${JSON.stringify(expected)}`);
}

function assertDurableRuntimeRunbook(value) {
  const label = "docs/reference/dev-runtime-boundary.md durable runtime runbook";
  for (const expected of [
    "DB live readiness and durable runtime readiness are separate gates",
    "runtime_durable_adapter_query_blocked",
    "DEV DB Provisioning Automation",
    "Source migration contract",
    "Live provisioning apply",
    "Static runtime boundary contract",
    "Public health observation",
    "Live DB read verification",
    "Live migration/repair apply",
    "Durable runtime postflight",
    "Do not run `kubectl get secret`",
    "secretValuesPrinted=false",
    "Before any authorized live migration or repair",
    "After an authorized migration/repair",
    "Cleared:",
    "Moved:",
    "Still blocked:",
    "No full M3, M4, or M5 acceptance is allowed while runtime durability is blocked",
    "bun cmd/hwlab-cloud-api/provision.ts --check",
    "bun cmd/hwlab-cloud-api/provision.ts --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json",
    "bun cmd/hwlab-cloud-api/provision.ts --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json",
    "bun cmd/hwlab-cloud-api/migrate.ts --check",
    "bun cmd/hwlab-cloud-api/migrate.ts --dry-run --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
    "bun cmd/hwlab-cloud-api/migrate.ts --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
    "DEV CD runtime migration Job uses",
    "node scripts/dev-runtime-provisioning.mjs --check",
    "node scripts/dev-runtime-provisioning.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json",
    "node scripts/dev-runtime-provisioning.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json",
    "node scripts/dev-runtime-migration.mjs --check",
    "node scripts/dev-runtime-migration.mjs --dry-run --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
    "node scripts/dev-runtime-migration.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
    "node scripts/dev-runtime-migration.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
    "node scripts/dev-runtime-postflight.mjs --check",
    "node scripts/dev-runtime-postflight.mjs --live --confirm-dev --confirmed-non-production --target api --report /tmp/hwlab-dev-gate/dev-runtime-postflight-report.json"
  ]) {
    assertIncludes(value, expected, label);
  }
}

function assertCloudApiImageRuntimeEntrypoints({ packageJson, artifactPublisher, gitopsRenderSource }) {
  const label = "cloud-api image runtime maintenance entrypoints";
  for (const expected of [
    "COPY internal ./internal",
    "COPY cmd ./cmd",
    "COPY scripts ./scripts",
    "COPY package.json ./package.json"
  ]) {
    assertIncludes(artifactPublisher, expected, `${label} artifact publisher`);
  }
  for (const expected of [
    "cmd/hwlab-cloud-api/provision.ts",
    "cmd/hwlab-cloud-api/migrate.ts"
  ]) {
    assertIncludes(packageJson.scripts?.check, `bun build ${expected} --target=bun --packages=external`, `${label} package check`);
    assertIncludes(packageJson.scripts?.check, `bun ${expected} --check`, `${label} package runtime check`);
  }
  for (const expected of ["scripts/artifact-publish.mjs", "runtime-dev", "runtime-prod"]) {
    assertIncludes(gitopsRenderSource, expected, `${label} node GitOps render source`);
  }
  for (const expected of ["name: \"prepare-source\"", "name: \"repo-reports-guard\"", "name: \"node-contract-check\"", "name: \"codex-api-forwarder-check\""]) {
    assertIncludes(gitopsRenderSource, expected, `${label} Tekton primitive CI task`);
  }
}

function assertGuardFixture(guard) {
  assert.equal(guard.guard, "hwlab-runtime-boundary");
  assert.equal(guard.schemaVersion, "v1");
  assertDevOnlyEnvironment(guard.environment, "guard.environment");
  assert.equal(guard.endpoint, DEV_ENDPOINT, "guard.endpoint");
  assertDevNamespace(guard.namespace, "guard.namespace");
  assert.equal(guard.policy?.runtimeSubstitution, "forbidden", "guard runtime substitution policy");
  assert.equal(guard.policy?.prodDeployment, "forbidden", "guard prod policy");
  assertForbiddenList(guard.policy?.forbiddenRuntimeSubstitutes, "guard forbidden substitutes");
  assertSameMembers(guard.requiredManifest?.requiredServiceIds, SERVICE_IDS, "guard manifest service ids");
  assertSameMembers(guard.requiredK3sSkeleton?.requiredWorkloadServiceIds, SERVICE_IDS, "guard workload service ids");
  assert.ok(
    guard.validation?.commands?.includes("node scripts/validate-runtime-boundary.mjs"),
    "guard must record executable validation command"
  );
}

function assertDeployManifest(manifest, guard) {
  assert.equal(manifest.manifestVersion, "v1");
  assertDevOnlyEnvironment(manifest.environment, "deploy manifest environment");
  assertDevNamespace(manifest.namespace, "deploy manifest namespace");
  assert.equal(manifest.endpoint, DEV_ENDPOINT, "deploy manifest endpoint");
  assert.ok(manifest.profiles?.dev?.enabled, "deploy manifest dev profile must be enabled");
  assertDevNamespace(manifest.profiles.dev.namespace, "deploy manifest dev profile namespace");
  assert.equal(manifest.profiles.dev.endpoint, DEV_ENDPOINT, "deploy manifest dev profile endpoint");
  assert.equal(manifest.profiles?.prod?.enabled, false, "deploy manifest prod profile must stay disabled");

  assertArray(manifest.services, "deploy manifest services");
  const serviceIds = manifest.services.map((service) => service.serviceId);
  assertUnique(serviceIds, "deploy manifest service ids");
  assertSameMembers(serviceIds, guard.requiredManifest.requiredServiceIds, "deploy manifest service ids");
  assertSameMembers(serviceIds, SERVICE_IDS, "deploy manifest service ids");

  for (const service of manifest.services) {
    assertServiceId(service.serviceId, `deploy service ${service.serviceId}`);
    assertHwlabImage(service.image, service.serviceId, `deploy service ${service.serviceId}`);
    assertDevNamespace(service.namespace, `${service.serviceId}.namespace`);
    assert.equal(service.profile, "dev", `${service.serviceId}.profile must be dev`);
    assert.match(service.healthPath, /^\//, `${service.serviceId}.healthPath must be absolute`);
    assertNoForbiddenSubstitutesDeep(service.env ?? {}, `${service.serviceId}.env`);
  }

  const cloudApi = manifest.services.find((service) => service.serviceId === "hwlab-cloud-api");
  assertForbiddenList(
    splitCsv(cloudApi?.env?.HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN),
    "hwlab-cloud-api HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN"
  );
}

function assertMasterHealthContract(contract, guard) {
  assertDevOnlyEnvironment(contract.environment, "master health contract environment");
  assert.equal(contract.endpoint, DEV_ENDPOINT, "master health contract endpoint");
  assert.equal(contract.prodAcceptance, false, "master health contract prodAcceptance");
  assert.equal(contract.reverseLink?.mode, guard.requiredHealthContract.reverseLink.mode, "reverse link mode");
  assert.equal(
    contract.reverseLink?.direction,
    guard.requiredHealthContract.reverseLink.direction,
    "reverse link direction"
  );
  assert.equal(contract.reverseLink?.client, "hwlab-tunnel-client", "reverse link client");
  assert.equal(contract.reverseLink?.publicPort, 16667, "reverse link public port");
  assertForbiddenList(contract.forbiddenRuntimeSubstitutes, "master forbidden substitutes");

  assertArray(contract.contracts, "master health contracts");
  const serviceIds = contract.contracts.map((item) => item.serviceId);
  assertUnique(serviceIds, "master health contract service ids");
  assertSameMembers(
    serviceIds,
    guard.requiredHealthContract.requiredContractServiceIds,
    "master health contract service ids"
  );
  for (const item of contract.contracts) {
    assertServiceId(item.serviceId, `master health contract ${item.serviceId}`);
  }
}

function assertWorkloads(workloads, guard) {
  assert.equal(workloads.kind, "List", "workloads kind");
  assertArray(workloads.items, "workloads items");

  const serviceIds = workloads.items.map((item) => item.metadata?.labels?.[serviceIdLabel]);
  assertUnique(serviceIds, "workload service ids");
  assertSameMembers(serviceIds, guard.requiredK3sSkeleton.requiredWorkloadServiceIds, "workload service ids");

  for (const workload of workloads.items) {
    const serviceId = workload.metadata?.labels?.[serviceIdLabel];
    assertServiceId(serviceId, `workload ${workload.metadata?.name}`);
    const expectedName = workload.kind === "Job" ? `${serviceId}-template` : serviceId;
    assert.equal(workload.metadata?.name, expectedName, `${serviceId} workload name`);
    assertDevNamespace(workload.metadata?.namespace, `${serviceId} workload namespace`);
    assert.equal(labelsFor(workload, serviceId)["app.kubernetes.io/name"], serviceId, `${serviceId} workload app label`);
    assertTemplateServiceId(workload, serviceId, `workload ${serviceId}`);

    const containers = containerList(workload, `workload ${serviceId}`);
    assert.ok(containers.length >= 1, `${serviceId} workload must define at least one container`);
    assertUnique(
      containers.map((container) => container.name),
      `${serviceId} container names`
    );

    const primaryContainer = containers.find((container) => container.name === serviceId);
    assert.ok(primaryContainer, `${serviceId} workload must include primary container ${serviceId}`);

    for (const container of containers) {
      assertHwlabImage(container.image, serviceId, `${serviceId} container ${container.name}`);
      assertNoForbiddenSubstitutesDeep(container.env ?? [], `${serviceId} container ${container.name} env`);
    }

    assertHealthProbe(primaryContainer, serviceId, `${serviceId} container ${serviceId}`);
  }
}

function assertServices(services, guard) {
  assert.equal(services.kind, "List", "services kind");
  assertArray(services.items, "services items");

  const runtimeServices = services.items;
  const serviceNames = runtimeServices.map((item) => item.metadata?.name);
  const serviceIds = [...new Set(runtimeServices.map((item) => item.metadata?.labels?.[serviceIdLabel]))];
  assertUnique(serviceNames, "k8s service names");
  assertSameMembers(serviceIds, guard.requiredK3sSkeleton.requiredServiceServiceIds, "k8s service ids");

  assert.equal(
    services.items.some((item) => item.metadata?.name === DEV_DB_ENV_CONTRACT.dns.serviceName),
    false,
    "cloud-api-db is an optional future alias and is not required in the current runtime boundary"
  );
  assert.equal(DEV_DB_ENV_CONTRACT.endpointAuthority.source, "secret-url-host", "DB readiness authority must be the Secret URL host");
  assert.equal(DEV_DB_ENV_CONTRACT.dns.requiredForReadiness, false, "DB DNS alias must not be a readiness hard requirement");
  assert.equal(DEV_DB_ENV_CONTRACT.dns.usedForProbe, false, "DB DNS alias must not be the runtime probe target");

  for (const service of runtimeServices) {
    const serviceId = service.metadata?.labels?.[serviceIdLabel];
    assertServiceId(serviceId, `k8s service ${service.metadata?.name}`);
    assertMetadataServiceId(service, serviceId, `k8s service ${serviceId}`);
    assertNoExternalNameSubstitute(service, `k8s service ${serviceId}`);
    if (service.metadata?.labels?.["hwlab.pikastech.local/instance-id"]) {
      assert.match(
        service.spec?.selector?.["statefulset.kubernetes.io/pod-name"],
        new RegExp(`^${escapeRegExp(serviceId)}-\\d+$`),
        `${serviceId} indexed selector`
      );
    } else {
      assert.equal(service.spec?.selector?.["app.kubernetes.io/name"], serviceId, `${serviceId} selector`);
    }
  }
}

function escapeRegExp(value) {
  return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function assertDevHealthConfig(configMap) {
  assert.equal(configMap.kind, "ConfigMap", "dev health contract kind");
  assertDevNamespace(configMap.metadata?.namespace, "dev health contract namespace");
  assert.equal(configMap.data?.endpoint, DEV_ENDPOINT, "dev health contract endpoint");
  assertRuntimePolicyText(configMap.data?.["runtime-substitute-policy"], "dev runtime substitute policy");
}

function assertProdDisabled(prodKustomization, prodDisabled) {
  assert.equal(prodKustomization.kind, "Kustomization", "prod kustomization kind");
  assert.deepEqual(prodKustomization.resources, ["prod-disabled.yaml"], "prod kustomization resources");
  assert.equal(prodDisabled.kind, "ConfigMap", "prod disabled kind");
  assert.equal(prodDisabled.metadata?.labels?.["hwlab.pikastech.local/profile"], "prod", "prod disabled profile");
  assert.equal(
    prodDisabled.metadata?.labels?.["hwlab.pikastech.local/acceptance"],
    "disabled",
    "prod disabled acceptance"
  );
  assert.equal(prodDisabled.data?.status, "disabled", "prod disabled status");
}

const guard = await readJSON("protocol/examples/runtime-boundary/guard.dev.json");
assertGuardFixture(guard);

assertDeployManifest(await readJSON(guard.requiredManifest.path), guard);
assertMasterHealthContract(await readJSON(guard.requiredHealthContract.path), guard);
assertWorkloads(await readJSON(guard.requiredK3sSkeleton.workloadsPath), guard);
assertServices(await readJSON(guard.requiredK3sSkeleton.servicesPath), guard);
assertKustomizationDevOnly(await readJSON(guard.requiredK3sSkeleton.devKustomizationPath), "dev kustomization");
assertDevHealthConfig(await readJSON(guard.requiredK3sSkeleton.devHealthContractPath));
assertProdDisabled(
  await readJSON(guard.requiredK3sSkeleton.prodKustomizationPath),
  await readJSON(guard.requiredK3sSkeleton.prodDisabledPath)
);
assertDurableRuntimeRunbook(await readText("docs/reference/dev-runtime-boundary.md"));
assertCloudApiImageRuntimeEntrypoints({
  packageJson: await readJSON("package.json"),
  artifactPublisher: await readText("scripts/artifact-publish.mjs"),
  gitopsRenderSource: await readText("scripts/gitops-render.mjs")
});

console.log(
  `validated HWLAB runtime boundary guard for ${SERVICE_IDS.length} services; forbidden substitutes: ${forbiddenRuntimeSubstitutes.join(", ")}`
);
