import { readConfig } from "./src/config";
import { runCiPublishUserServiceDryRunPreflight, type PublishPreflightTransport } from "./src/ci";
import { autoRemoteCiPublishUserServiceDryRunPlan } from "./src/remote";

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

const commit = "0123456789abcdef0123456789abcdef01234567";
const config = readConfig();

const infraBlockedTransport: PublishPreflightTransport = {
  coreFetch: async (path) => ({
    ok: false,
    status: 503,
    body: {
      ok: false,
      failureKind: "target-stack-not-running",
      runnerDisposition: "infra-blocked",
      degradedReason: "backend-core-container-missing",
      message: `backend-core unavailable for ${path}`,
    },
  }),
  dispatchHostSsh: async (command, waitMs, remoteTimeoutMs) => ({
    ok: false,
    taskId: null,
    status: "infra-blocked",
    stdout: "",
    stderr: `backend-core bridge unavailable while dispatching readonly SSH task (${waitMs}/${remoteTimeoutMs})`,
    exitCode: null,
    raw: {
      ok: false,
      failureKind: "target-stack-not-running",
      runnerDisposition: "infra-blocked",
      command,
    },
  }),
  commandCwd: "/workspace/unidesk",
  artifactRegistryCommand: (probe) => [process.execPath, "scripts/cli.ts", "ssh", probe.providerId, "argv", "bash", "-lc", probe.script],
};

async function main(): Promise<void> {
  const autoRemote = autoRemoteCiPublishUserServiceDryRunPlan(config, [
    "ci",
    "publish-user-service",
    "--service",
    "frontend",
    "--commit",
    commit,
    "--dry-run",
  ], {
    CODE_QUEUE_SERVICE_ROLE: "scheduler",
    CODE_QUEUE_DEV_CONTAINER_MASTER_HOST: "74.48.78.17",
  });
  assertCondition(autoRemote.enabled === true, "Code Queue runner publish dry-run should auto-select remote frontend transport", autoRemote);
  assertCondition(autoRemote.host === "74.48.78.17", "auto remote plan should use CODE_QUEUE_DEV_CONTAINER_MASTER_HOST", autoRemote);
  assertCondition(autoRemote.transport === "frontend", "auto remote plan should use frontend transport", autoRemote);
  assertCondition(String(autoRemote.command ?? "").includes("--main-server-ip 74.48.78.17"), "auto remote plan should expose command shape", autoRemote);

  const nonRunner = autoRemoteCiPublishUserServiceDryRunPlan(config, [
    "ci",
    "publish-user-service",
    "--service",
    "frontend",
    "--commit",
    commit,
    "--dry-run",
  ], {});
  assertCondition(nonRunner.enabled === false, "non-runner local CLI should not auto-remote without a host hint", nonRunner);
  assertCondition(nonRunner.failureClassification === "local-docker-required", "non-runner disabled plan should classify local docker requirement", nonRunner);

  const result = await runCiPublishUserServiceDryRunPreflight(config, [
    "publish-user-service",
    "--service",
    "frontend",
    "--commit",
    commit,
    "--dry-run",
  ], infraBlockedTransport);

  const record = asRecord(result, "preflight");
  const source = asRecord(record.source, "source");
  const channels = Array.isArray(record.channels) ? record.channels.map((item) => asRecord(item, "channel")) : [];
  const controlChannels = Array.isArray(record.controlChannels) ? record.controlChannels.map((item) => asRecord(item, "control channel")) : [];
  const registry = asRecord(record.registry, "registry");
  const controlledPublish = asRecord(record.controlledPublish, "controlledPublish");
  const controlPlane = asRecord(record.controlPlane, "controlPlane");
  const backendCore = asRecord(channels.find((item) => item.channel === "backend-core-api")?.detail, "backendCore detail");
  const backendCoreTransport = asRecord(backendCore.detail, "backendCore transport payload");
  const backendCoreBody = asRecord(backendCoreTransport.body, "backendCore body payload");
  const providerDispatch = asRecord(channels.find((item) => item.channel === "provider-dispatch")?.detail, "providerDispatch detail");
  const missingChannels = Array.isArray(record.missingChannels) ? record.missingChannels as string[] : [];
  const missingControlChannels = Array.isArray(record.missingControlChannels) ? record.missingControlChannels as string[] : [];

  assertCondition(record.ok === false, "infra-blocked preflight should fail", record);
  assertCondition(record.mode === "dry-run-preflight", "dry-run preflight mode should be reported", record);
  assertCondition(record.runnerDisposition === "infra-blocked", "runnerDisposition should be infra-blocked", record);
  assertCondition(record.failureClassification === "local-docker-required", "local backend-core absence should classify as local-docker-required", record);
  assertCondition(controlPlane.transport === "local-docker", "local preflight should expose local-docker transport", controlPlane);
  assertCondition(controlPlane.remoteCapable === false, "local preflight should not claim remote-capable transport", controlPlane);
  assertCondition(controlPlane.preferredRunnerPath === "frontend-private-proxy", "controlPlane should name frontend private proxy as preferred path", controlPlane);
  assertCondition(Array.isArray(record.missingChannels), "missingChannels should be an array", record);
  assertCondition(Array.isArray(record.missingControlChannels), "missingControlChannels should be an array", record);
  assertCondition(Array.isArray(record.failedScopes), "failedScopes should be an array", record);
  assertCondition((record.failedScopes as unknown[]).includes("local-docker-control-plane"), "failedScopes should expose publish-blocking registry scope", record);
  assertCondition(missingChannels.includes("backend-core-api"), "backend-core-api should be missing", record);
  assertCondition(missingChannels.includes("database"), "database should be missing", record);
  assertCondition(missingChannels.includes("provider-dispatch"), "provider-dispatch should be missing", record);
  assertCondition(missingChannels.includes("provider-host-ssh"), "provider-host-ssh should be missing", record);
  assertCondition(missingChannels.includes("artifact-registry"), "artifact-registry should be missing", record);
  assertCondition(missingControlChannels.join(",") === "backend-core,database,provider,registry", "missingControlChannels should name runner-facing domains", record);
  assertCondition(controlChannels.length === 4, "controlChannels should report four runner-facing domains", controlChannels);
  assertCondition(controlChannels.every((item) => item.ok === false), "infra-blocked transport should fail every control channel", controlChannels);
  assertCondition(
    (controlChannels.find((item) => item.channel === "provider")?.probes as unknown[]).join(",") === "provider-dispatch,provider-host-ssh",
    "provider control channel should map provider dispatch and host ssh probes",
    controlChannels,
  );
  assertCondition(!JSON.stringify(record).includes("No such container: unidesk-database"), "raw container error should not leak", record);
  assertCondition(backendCoreBody.failureKind === "target-stack-not-running", "backend-core detail should classify target-stack-not-running", backendCoreBody);
  assertCondition(providerDispatch.status === "infra-blocked", "provider dispatch should be infra-blocked", providerDispatch);
  assertCondition(registry.ok === false, "registry channel should fail without backend-core bridge", registry);
  assertCondition(Array.isArray(channels) && channels.length >= 5, "expected five channel probes", channels);
  assertCondition(source.mode === "planned-only", "source should remain planned-only", source);
  assertCondition(source.repoFetchUrl === "git@github.com:pikasTech/unidesk.git", "source repo should use CI catalog ssh form", source);
  assertCondition(asRecord(record.artifactSummary, "artifactSummary").imageRef === `127.0.0.1:5000/unidesk/frontend:${commit}`, "artifact ref should remain commit-pinned", record.artifactSummary);
  assertCondition(controlledPublish.environment === "D601", "controlledPublish should name the controlled environment", controlledPublish);
  assertCondition(controlledPublish.namespace === "unidesk-ci", "controlledPublish should name the Tekton namespace", controlledPublish);
  assertCondition(controlledPublish.pipeline === "unidesk-user-service-artifact-publish", "controlledPublish should name the user-service pipeline", controlledPublish);
  assertCondition(String(controlledPublish.command ?? "").includes("--wait-ms 1200000"), "controlledPublish should provide the real publish command shape", controlledPublish);
  assertCondition(String(record.boundary ?? "").includes("read-only"), "boundary should state preflight is read-only", record);

  process.stdout.write(`${JSON.stringify({
    ok: true,
    checks: [
      "dry-run preflight returns infra-blocked when backend-core/database/provider channels are absent",
      "missing channel list names the absent channels",
      "artifact summary remains commit-pinned and read-only",
      "missingControlChannels maps detailed probes to backend-core/database/provider/registry",
      "controlledPublish identifies D601 unidesk-ci as the only place for the real publish",
      "runner-like environments auto-select the existing frontend remote transport",
      "local backend-core absence is classified as local-docker-required",
    ],
    missingChannels: record.missingChannels,
    missingControlChannels: record.missingControlChannels,
    registry,
  }, null, 2)}\n`);
}

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