import { spawnSync } from "node:child_process";
import { readConfig } from "./src/config";
import { runCiPublishBackendCoreDryRunPreflight, type PublishPreflightTransport } from "./src/ci";
import { artifactRegistryReadonlyResultFromCommand, buildArtifactRegistryReadonlyProbe, parseArtifactRegistryOptions } from "./src/artifact-registry";
import type { CommandResult } from "./src/command";

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 asStringArray(value: unknown, label: string): string[] {
  assertCondition(Array.isArray(value), `${label} must be an array`, { value });
  return (value as unknown[]).map(String);
}

function command(overrides: Partial<CommandResult>): CommandResult {
  return {
    command: ["frontend", "/api/dispatch", "D601", "host.ssh", "health"],
    cwd: ".",
    exitCode: 0,
    stdout: "",
    stderr: "",
    signal: null,
    timedOut: false,
    ...overrides,
  };
}

function registryDriftStdout(): string {
  return [
    "readonly=true",
    "unit_exists=true",
    "compose_exists=true",
    "config_exists=true",
    "storage_exists=true",
    "systemctl_available=true",
    "unit_active=active",
    "unit_enabled=enabled",
    "docker_available=true",
    "container_running=true",
    "container_status=running",
    "container_image=registry:2.8.3",
    "container_restart_policy=unless-stopped",
    "listener_count=1",
    "bad_listener_count=0",
    "loopback_only=true",
    "curl_available=true",
    "v2_http_code=200",
    "config_hash=contract-config-observed",
    "compose_hash=contract-compose",
    "unit_hash=contract-unit-observed",
    "expected_config_hash=contract-config-expected",
    "expected_compose_hash=contract-compose",
    "expected_unit_hash=contract-unit-expected",
    "config_hash_matches=false",
    "compose_hash_matches=true",
    "unit_hash_matches=false",
    "image_matches=false",
    "",
  ].join("\n");
}

async function main(): Promise<void> {
  const commit = "0123456789abcdef0123456789abcdef01234567";
  const config = readConfig();
  const registryOptions = parseArtifactRegistryOptions(["--provider-id", "D601"]);
  const registryProbe = buildArtifactRegistryReadonlyProbe("health", registryOptions);
  const registry = asRecord(artifactRegistryReadonlyResultFromCommand(registryProbe, command({
    stdout: registryDriftStdout(),
  })), "registry health");

  const transport: PublishPreflightTransport = {
    kind: "remote-frontend",
    remoteHost: "https://example.invalid",
    coreFetch: async () => ({
      ok: true,
      status: 200,
      body: { ok: true, dbReady: true },
    }),
    dispatchHostSsh: async (remoteCommand) => {
      if (remoteCommand.includes("/v2/")) {
        return {
          ok: true,
          taskId: "task-registry",
          status: "succeeded",
          stdout: registryDriftStdout(),
          stderr: "",
          exitCode: 0,
          raw: {},
        };
      }
      return {
        ok: true,
        taskId: "task-ci-runner",
        status: "succeeded",
        stdout: [
          "provider_host_ssh=ok",
          "kubectl=ok",
          "docker=ok",
          "docker_socket=true",
          "namespace=true",
          "tekton_pipeline=true",
          "tekton_task=true",
          "service_account=true",
          "pvc=true",
          "source_parent_directory=true",
        ].join("\n"),
        stderr: "",
        exitCode: 0,
        raw: {},
      };
    },
    commandCwd: "/workspace/unidesk",
    artifactRegistryCommand: (probe) => ["mock", probe.action, probe.remoteCommandShape],
  };

  const publish = asRecord(await runCiPublishBackendCoreDryRunPreflight(config, [
    "publish-backend-core",
    "--commit",
    commit,
    "--dry-run",
  ], transport), "publish dry-run");
  const publishRequirements = asRecord(publish.artifactRequirements, "publish artifactRequirements");
  const publishLabels = asRecord(publishRequirements.requiredLabels, "publish requiredLabels");
  const publishSummary = asRecord(publish.artifactSummary, "publish artifactSummary");
  const publishDevApplyPath = asRecord(publish.devApplyPath, "publish devApplyPath");

  const applyResult = spawnSync("bun", [
    "scripts/cli.ts",
    "deploy",
    "apply",
    "--env",
    "dev",
    "--service",
    "backend-core",
    "--commit",
    commit,
    "--dry-run",
  ], {
    cwd: process.cwd(),
    encoding: "utf8",
    maxBuffer: 8 * 1024 * 1024,
  });
  assertCondition(applyResult.status === 0, "dev deploy dry-run should exit 0", {
    status: applyResult.status,
    stdoutTail: applyResult.stdout.slice(-2000),
    stderrTail: applyResult.stderr.slice(-2000),
  });
  const applyEnvelope = asRecord(JSON.parse(applyResult.stdout) as unknown, "deploy apply envelope");
  const applyData = asRecord(applyEnvelope.data, "deploy apply data");
  const applyItems = Array.isArray(applyData.results) ? applyData.results : [];
  assertCondition(applyItems.length === 1, "dev deploy dry-run should return one backend-core item", applyData);
  const apply = asRecord(applyItems[0], "deploy apply backend-core");
  const applySource = asRecord(apply.source, "deploy apply source");
  const applyRegistry = asRecord(apply.registry, "deploy apply registry");
  const applyBuild = asRecord(apply.build, "deploy apply build");
  const applyLabels = asRecord(apply.requiredLabels, "deploy apply requiredLabels");
  const applyProbe = asRecord(apply.registryProbe, "deploy apply registryProbe");
  const registryFailedScopes = asStringArray(registry.failedScopes, "registry failedScopes");
  const publishBlockedScopes = asStringArray(publish.blockedScopes, "publish blockedScopes");

  assertCondition(registry.ok === false, "registry health drift should be blocking", registry);
  assertCondition(registry.failureClassification === "registry-unhealthy", "registry drift should classify registry-unhealthy", registry);
  assertCondition(registryFailedScopes.includes("rendered-config"), "registry drift must expose rendered-config", registry);
  assertCondition(registryFailedScopes.includes("registry-image"), "registry drift must expose registry-image", registry);
  assertCondition(publish.ok === false, "publish preflight must block on registry drift", publish);
  assertCondition(publish.failureClassification === registry.failureClassification, "publish failure classification should match registry health", publish);
  assertCondition(publishBlockedScopes.includes("rendered-config"), "publish blockedScopes must include registry rendered-config drift", publish);
  assertCondition(publishBlockedScopes.includes("registry-image"), "publish blockedScopes must include registry image drift", publish);

  assertCondition(publish.sourceRepo === apply.sourceRepo, "sourceRepo should match between publish and dev deploy dry-run", { publish, apply });
  assertCondition(publish.targetCommit === apply.commit, "targetCommit should match dev deploy commit", { publish, apply });
  assertCondition(publishSummary.imageRef === apply.sourceImage, "publish artifact image must match dev deploy sourceImage", { publishSummary, apply });
  assertCondition(publish.registryTarget === "127.0.0.1:5000/unidesk/backend-core", "publish registry target mismatch", publish);
  assertCondition(applyRegistry.imageRef === publishSummary.imageRef, "dev deploy registry image should match artifactSummary imageRef", { applyRegistry, publishSummary });
  assertCondition(applyRegistry.digest === null && publishSummary.digest === null, "dry-runs must not fake digest values", { applyRegistry, publishSummary });
  assertCondition(applyRegistry.digestHeader === "Docker-Content-Digest", "dev deploy must name digest header", applyRegistry);
  assertCondition(applyProbe.digestHeader === "Docker-Content-Digest", "registry probe must name digest header", applyProbe);
  assertCondition(applyBuild.willCompile === false, "dev deploy CD must not compile", applyBuild);
  assertCondition(applyBuild.willRunCargoBuild === false, "dev deploy CD must not run cargo build", applyBuild);
  assertCondition(applyBuild.willRunDockerBuild === false, "dev deploy CD must not run docker build", applyBuild);
  assertCondition(applyBuild.producerBoundary === "ci publish-backend-core", "dev deploy producer boundary mismatch", applyBuild);
  assertCondition(publishDevApplyPath.pullOnly === true, "publish devApplyPath must be pull-only", publishDevApplyPath);
  assertCondition(String(publishDevApplyPath.dryRun ?? "").includes("deploy apply --env dev --service backend-core"), "publish devApplyPath must expose dev apply dry-run", publishDevApplyPath);
  assertCondition(!String(publishDevApplyPath.apply ?? "").includes("--env prod"), "publish devApplyPath must not point to prod", publishDevApplyPath);

  for (const [key, value] of Object.entries(publishLabels)) {
    assertCondition(applyLabels[key] === value, `deploy apply required label ${key} should match publish preflight`, { publishLabels, applyLabels });
  }
  assertCondition(applySource.repo === publish.sourceRepo, "deploy apply source.repo should match publish sourceRepo", { applySource, publish });
  assertCondition(applySource.commit === publish.targetCommit, "deploy apply source.commit should match publish targetCommit", { applySource, publish });

  process.stdout.write(`${JSON.stringify({
    ok: true,
    checks: [
      "backend-core publish dry-run, registry health drift, and dev deploy dry-run agree on source repo, commit, registry image, labels, digest header, and no-build CD",
      "registry drift remains a blocker with rendered-config and registry-image failed scopes",
      "publish readiness exposes a dev apply dry-run path and never points to prod",
    ],
    blockedScopes: publishBlockedScopes,
  }, null, 2)}\n`);
}

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