import { readConfig } from "./src/config";
import { runCiPublishBackendCoreDryRunPreflight, 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();

function healthyRegistryStdout(): string {
  return [
    "systemctl_available=true",
    "docker_available=true",
    "curl_available=true",
    "unit_exists=true",
    "unit_active=active",
    "unit_enabled=enabled",
    "compose_exists=true",
    "config_exists=true",
    "storage_exists=true",
    "container_running=true",
    "container_status=running",
    "container_image=registry:2.8.3",
    "container_restart_policy=always",
    "listener_count=1",
    "bad_listener_count=0",
    "loopback_only=true",
    "v2_http_code=200",
    "image_matches=true",
    "config_hash_matches=true",
    "compose_hash_matches=true",
    "unit_hash_matches=true",
  ].join("\n");
}

function readyTransport(kind: "remote-frontend" | "local-docker" = "remote-frontend"): PublishPreflightTransport {
  return {
    kind,
    remoteHost: kind === "remote-frontend" ? "https://example.invalid" : null,
    coreFetch: async () => ({
      ok: true,
      status: 200,
      body: {
        ok: true,
        dbReady: true,
      },
    }),
    dispatchHostSsh: async (command) => {
      if (command.includes("kubectl get namespace unidesk-ci")) {
        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: {},
        };
      }
      if (command.includes("/v2/")) {
        return {
          ok: true,
          taskId: "task-registry",
          status: "succeeded",
          stdout: healthyRegistryStdout(),
          stderr: "",
          exitCode: 0,
          raw: {},
        };
      }
      return {
        ok: true,
        taskId: "task-host-ssh",
        status: "succeeded",
        stdout: "provider_host_ssh=ok\n",
        stderr: "",
        exitCode: 0,
        raw: {},
      };
    },
    commandCwd: "/workspace/unidesk",
    artifactRegistryCommand: (probe) => ["mock", probe.action, probe.remoteCommandShape],
  };
}

const localMissingTransport: PublishPreflightTransport = {
  kind: "local-docker",
  coreFetch: async () => ({
    ok: false,
    status: 503,
    body: {
      ok: false,
      failureKind: "target-stack-not-running",
      runnerDisposition: "infra-blocked",
      degradedReason: "backend-core-container-missing",
      message: "backend-core unavailable from runner-local Docker",
    },
  }),
  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) => ["mock", probe.action, probe.remoteCommandShape],
};

const remoteInfraBlockedTransport: PublishPreflightTransport = {
  kind: "remote-frontend",
  remoteHost: "https://example.invalid",
  coreFetch: async () => ({
    ok: true,
    status: 200,
    body: {
      ok: true,
      dbReady: true,
    },
  }),
  dispatchHostSsh: async (command) => {
    if (command.includes("/v2/")) {
      return {
        ok: true,
        taskId: "task-registry",
        status: "succeeded",
        stdout: [
          "systemctl_available=true",
          "docker_available=true",
          "curl_available=true",
          "unit_exists=true",
          "unit_active=active",
          "unit_enabled=enabled",
          "compose_exists=true",
          "config_exists=true",
          "storage_exists=true",
          "container_running=false",
          "container_status=exited",
          "container_image=registry:2.8.3",
          "container_restart_policy=always",
          "listener_count=0",
          "bad_listener_count=0",
          "loopback_only=false",
          "v2_http_code=000",
          "image_matches=true",
          "config_hash_matches=true",
          "compose_hash_matches=true",
          "unit_hash_matches=true",
        ].join("\n"),
        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],
};

async function main(): Promise<void> {
  const autoRemote = autoRemoteCiPublishUserServiceDryRunPlan(config, [
    "ci",
    "publish-backend-core",
    "--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 backend-core dry-run should auto-select remote frontend transport", autoRemote);
  assertCondition(autoRemote.host === "74.48.78.17", "auto remote backend-core plan should use CODE_QUEUE_DEV_CONTAINER_MASTER_HOST", autoRemote);
  assertCondition(String(autoRemote.command ?? "").includes("ci publish-backend-core"), "auto remote command should preserve backend-core publish dry-run", autoRemote);

  const localMissing = await runCiPublishBackendCoreDryRunPreflight(config, [
    "publish-backend-core",
    "--commit",
    commit,
    "--dry-run",
  ], localMissingTransport);
  const localMissingRecord = asRecord(localMissing, "local missing backend-core preflight");
  const localMissingControlPlane = asRecord(localMissingRecord.remoteControlPlaneCandidate, "local missing control plane");
  const localBlockedScopes = Array.isArray(localMissingRecord.blockedScopes) ? localMissingRecord.blockedScopes as string[] : [];
  assertCondition(localMissingRecord.ok === false, "local missing backend-core preflight should fail", localMissingRecord);
  assertCondition(localMissingRecord.failureClassification === "local-docker-required", "local Docker absence should classify local-docker-required", localMissingRecord);
  assertCondition(localMissingControlPlane.transport === "local-docker", "local missing control plane should state local transport", localMissingControlPlane);
  assertCondition(localMissingControlPlane.remoteCapable === false, "local missing control plane should not claim remote capable", localMissingControlPlane);
  assertCondition(String(localMissingControlPlane.remoteCommandShape ?? "").includes("publish-backend-core"), "fallback should expose backend-core remote command shape", localMissingControlPlane);
  assertCondition(localBlockedScopes.includes("local-docker-control-plane"), "local missing blockedScopes should expose local-docker-control-plane", localMissingRecord);
  assertCondition(asRecord(localMissingRecord.d601Ci, "local missing d601Ci").dryRunWillCompileRust === false, "local dry-run must not compile Rust", localMissingRecord);

  const result = await runCiPublishBackendCoreDryRunPreflight(config, [
    "publish-backend-core",
    "--commit",
    commit,
    "--dry-run",
  ], readyTransport());

  const record = asRecord(result, "backend-core preflight");
  const source = asRecord(record.source, "source");
  const sourceAuth = asRecord(record.sourceAuth, "sourceAuth");
  const d601Ci = asRecord(record.d601Ci, "d601Ci");
  const ciRunner = asRecord(record.ciRunner, "ciRunner");
  const artifactSummary = asRecord(record.artifactSummary, "artifactSummary");
  const artifactRequirements = asRecord(record.artifactRequirements, "artifactRequirements");
  const requiredLabels = asRecord(artifactRequirements.requiredLabels, "requiredLabels");
  const devApplyPath = asRecord(record.devApplyPath, "devApplyPath");
  const controlledPublish = asRecord(record.controlledPublish, "controlledPublish");
  const controlPlane = asRecord(record.remoteControlPlaneCandidate, "remoteControlPlaneCandidate");
  const blockedScopes = Array.isArray(record.blockedScopes) ? record.blockedScopes as string[] : [];

  assertCondition(record.ok === true, "ready backend-core preflight should pass", record);
  assertCondition(record.mode === "dry-run-preflight", "backend-core dry-run must be preflight mode", record);
  assertCondition(record.targetCommit === commit, "target commit should be surfaced", record);
  assertCondition(record.sourceRepo === "https://github.com/pikasTech/unidesk", "source repo should come from CI.json", record);
  assertCondition(record.registryTarget === "127.0.0.1:5000/unidesk/backend-core", "registry target should be D601 backend-core repository", record);
  assertCondition(record.wouldBuildOnD601 === true, "preflight should state real publish would build on D601", record);
  assertCondition(record.dryRunBuildStarted === false, "dry-run must not start a build", record);
  assertCondition(blockedScopes.length === 0, "ready preflight should have no blocked scopes", record);
  assertCondition(record.failureClassification === null, "ready preflight should not classify a failure", record);
  assertCondition(ciRunner.environment === "D601", "ciRunner should name D601", ciRunner);
  assertCondition(controlPlane.transport === "remote-frontend", "ready remote preflight should expose remote frontend transport", controlPlane);
  assertCondition(String(controlPlane.remoteCommandShape ?? "").includes("publish-backend-core"), "controlPlane command shape should be backend-core-specific", controlPlane);
  assertCondition(source.repoFetchUrl === "git@github.com:pikasTech/unidesk.git", "source auth should use GitHub SSH fetch URL", source);
  assertCondition(sourceAuth.egressProxy === "http://127.0.0.1:18789", "source auth should name provider egress proxy", sourceAuth);
  assertCondition(d601Ci.wouldBuildOnD601 === true, "D601 CI runner preflight should expose D601 build boundary", d601Ci);
  assertCondition(artifactSummary.imageRef === `127.0.0.1:5000/unidesk/backend-core:${commit}`, "artifact should be commit-pinned", artifactSummary);
  assertCondition(artifactSummary.digest === null, "dry-run must not fake digest", artifactSummary);
  assertCondition(requiredLabels["unidesk.ai/service-id"] === "backend-core", "service id label should be required", requiredLabels);
  assertCondition(requiredLabels["unidesk.ai/source-commit"] === commit, "source commit label should be required", requiredLabels);
  assertCondition(devApplyPath.pullOnly === true, "dev apply path should be pull-only", devApplyPath);
  assertCondition(String(devApplyPath.apply ?? "").includes("deploy apply --env dev --service backend-core"), "dev apply command should be surfaced", devApplyPath);
  assertCondition(!String(devApplyPath.apply ?? "").includes("--env prod"), "dev apply path must not point to prod", devApplyPath);
  assertCondition(controlledPublish.environment === "D601", "controlled publish should name D601", controlledPublish);
  assertCondition(String(record.boundary ?? "").includes("no Rust compile"), "boundary should explicitly forbid Rust compile during dry-run", record);
  assertCondition(String(record.recommendedAction ?? "").includes("ci publish-backend-core"), "recommended action should name real publish command", record);

  const remoteBlocked = await runCiPublishBackendCoreDryRunPreflight(config, [
    "publish-backend-core",
    "--commit",
    commit,
    "--dry-run",
  ], remoteInfraBlockedTransport);
  const remoteBlockedRecord = asRecord(remoteBlocked, "remote infra-blocked backend-core preflight");
  const remoteBlockedControlPlane = asRecord(remoteBlockedRecord.remoteControlPlaneCandidate, "remote infra-blocked control plane");
  const remoteBlockedScopes = Array.isArray(remoteBlockedRecord.blockedScopes) ? remoteBlockedRecord.blockedScopes as string[] : [];
  assertCondition(remoteBlockedRecord.ok === false, "remote infra-blocked preflight should fail", remoteBlockedRecord);
  assertCondition(remoteBlockedRecord.failureClassification === "registry-unhealthy" || remoteBlockedRecord.failureClassification === "registry-not-installed", "remote infra-blocked should classify registry failure, not local docker", remoteBlockedRecord);
  assertCondition(remoteBlockedControlPlane.transport === "remote-frontend", "remote infra-blocked should keep remote frontend transport", remoteBlockedControlPlane);
  assertCondition(!remoteBlockedScopes.includes("local-docker-control-plane"), "remote infra-blocked must not blame runner local Docker", remoteBlockedRecord);
  assertCondition(remoteBlockedScopes.includes("registry-container") || remoteBlockedScopes.includes("registry-api") || remoteBlockedScopes.includes("provider-ssh-command"), "remote infra-blocked should expose registry/provider scope", remoteBlockedRecord);
  assertCondition(String(remoteBlockedRecord.recommendedAction ?? "").includes("registry"), "remote infra-blocked should recommend registry recovery", remoteBlockedRecord);

  process.stdout.write(`${JSON.stringify({
    ok: true,
    checks: [
      "runner backend-core dry-run auto-selects the remote frontend control plane",
      "local-docker-required fallback is explicit and exposes a backend-core remote command shape",
      "remote ready preflight reports D601/registry/artifact requirements without starting CI",
      "remote infra-blocked preflight does not misclassify runner-local Docker absence",
      "backend-core dry-run exposes target commit, source repo, D601 runner and registry target",
      "backend-core dry-run reports wouldBuildOnD601 without starting a build",
      "artifact labels and digest requirements are visible without faking a digest",
      "standard path is publish artifact, verify labels/digest, then dev pull-only apply",
    ],
    localBlockedScopes,
    readyBlockedScopes: record.blockedScopes,
    remoteBlockedScopes,
  }, null, 2)}\n`);
}

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