import { readFileSync } from "node:fs";
import type { UniDeskConfig } from "./config";
import { rootPath } from "./config";
import { capture, compactCapture, parseJsonOutput, shQuote } from "./platform-infra-ops-library";

export interface PacNodeStatusTarget {
  id: string;
  route: string;
  namespace: string;
  role: string;
}

export interface PacNodeStatusConsumer {
  id: string;
  node: string;
  lane: string;
  namespace: string;
  pipeline: string;
  pipelineRunPrefix: string;
  argoNamespace: string;
  argoApplication: string;
  repo: string;
  repository: string;
  params: Record<string, string>;
  deliveryProvenance: Record<string, unknown> | null;
  expectedSourceCommit?: string;
}

interface PacNodeStatusCaptureOptions {
  config: UniDeskConfig;
  target: PacNodeStatusTarget;
  consumers: PacNodeStatusConsumer[];
  timeoutMs: number;
  configPath: string;
}

const snapshotScriptFile = rootPath("scripts", "native", "cicd", "pac-node-status-snapshot.sh");
const evaluatorFile = rootPath("scripts", "native", "cicd", "pac-status-evaluator.cjs");

export async function capturePacNodeStatusSnapshot(options: PacNodeStatusCaptureOptions): Promise<Record<string, unknown>> {
  const startedAt = Date.now();
  const pipelineRunSelector = pacPipelineRunSelector(options.consumers);
  const pipelineRunNamespaces = [...new Set(options.consumers
    .map((consumer) => consumer.namespace)
    .filter((namespace) => /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u.test(namespace)))]
    .sort();
  const script = [
    `export UNIDESK_PAC_READ_STEP_TIMEOUT_SECONDS=${shQuote(String(Math.max(1, Math.ceil(options.timeoutMs / 1_000))))}`,
    `export UNIDESK_PAC_PIPELINERUN_SELECTOR=${shQuote(pipelineRunSelector)}`,
    `export UNIDESK_PAC_PIPELINERUN_NAMESPACES=${shQuote(pipelineRunNamespaces.join(" "))}`,
    `export UNIDESK_PAC_NODE_CONSUMERS_B64=${shQuote(Buffer.from(JSON.stringify(options.consumers), "utf8").toString("base64"))}`,
    `export UNIDESK_PAC_EVALUATOR_B64=${shQuote(Buffer.from(readFileSync(evaluatorFile, "utf8"), "utf8").toString("base64"))}`,
    readFileSync(snapshotScriptFile, "utf8"),
  ].join("\n");
  printProgress(options, "started", 0, null);
  const result = await capture(options.config, options.target.route, ["sh"], script, { runtimeTimeoutMs: options.timeoutMs });
  const elapsedMs = Date.now() - startedAt;
  const parsed = parseJsonOutput(result.stdout);
  const progress = parseNodeSnapshotProgress(result.stderr);
  const status = result.exitCode === 124
    ? "timeout"
    : result.exitCode !== 0
      ? "remote-failure"
      : parsed === null
        ? result.stdout.trim().length === 0 ? "empty-response" : "parse-failure"
        : "succeeded";
  const firstBreak = status === "succeeded"
    ? "none"
    : result.exitCode === 124 && progress.remoteStage !== null
      ? `pac-node-${progress.remoteStage}-read-timeout`
      : `pac-node-read-${status}`;
  printProgress(options, status, elapsedMs, { exitCode: result.exitCode, firstBreak });
  const observation = {
    ok: status === "succeeded",
    status,
    target: options.target.id,
    consumer: "*",
    stage: "node-status-remote-observe",
    elapsedMs,
    timeoutMs: options.timeoutMs,
    configPath: options.configPath,
    exitCode: result.exitCode,
    stdoutBytes: Buffer.byteLength(result.stdout, "utf8"),
    stderrBytes: Buffer.byteLength(result.stderr, "utf8"),
    partial: parsed !== null && parsed.ok !== true,
    remoteStage: progress.remoteStage,
    remoteStageStatus: progress.remoteStageStatus,
    lastSuccessfulStage: progress.lastSuccessfulStage,
    firstBreak: { code: firstBreak, phase: "node-status-remote-observe" },
    valuesPrinted: false,
  };
  if (parsed !== null) return { ...parsed, observation, remoteCaptureCount: 1, mutation: false };
  return {
    ok: false,
    mutation: false,
    consumers: options.consumers.map((consumer) => unavailableRow(consumer, firstBreak)),
    readErrors: [{
      stage: progress.remoteStage ?? "node-status-remote-observe",
      code: firstBreak,
      exitCode: result.exitCode,
      lastSuccessfulStage: progress.lastSuccessfulStage,
      valuesPrinted: false,
    }],
    observation,
    remote: compactCapture(result, { full: true }),
    remoteCaptureCount: 1,
    valuesPrinted: false,
  };
}

function pacPipelineRunSelector(consumers: readonly PacNodeStatusConsumer[]): string {
  const labels = ["app.kubernetes.io/managed-by=pipelinesascode.tekton.dev"];
  const repositories = [...new Set(consumers
    .map((consumer) => consumer.repository)
    .filter((repository) => /^[a-z0-9]([-a-z0-9.]*[a-z0-9])?$/u.test(repository)))]
    .sort();
  if (repositories.length === 1) {
    labels.push(`pipelinesascode.tekton.dev/repository=${repositories[0]}`);
  } else if (repositories.length > 1) {
    labels.push(`pipelinesascode.tekton.dev/repository in (${repositories.join(",")})`);
  }
  return labels.join(",");
}

function parseNodeSnapshotProgress(stderr: string): {
  remoteStage: string | null;
  remoteStageStatus: string | null;
  lastSuccessfulStage: string | null;
} {
  let remoteStage: string | null = null;
  let remoteStageStatus: string | null = null;
  let lastSuccessfulStage: string | null = null;
  for (const line of stderr.split(/\r?\n/u)) {
    const parsed = parseJsonOutput(line);
    if (parsed?.event !== "pac-node-snapshot-progress") continue;
    const stage = typeof parsed.stage === "string" ? parsed.stage : null;
    const stageStatus = typeof parsed.status === "string" ? parsed.status : null;
    if (stage === null || stageStatus === null) continue;
    remoteStage = stage;
    remoteStageStatus = stageStatus;
    if (stageStatus === "completed") lastSuccessfulStage = stage;
  }
  return { remoteStage, remoteStageStatus, lastSuccessfulStage };
}

function unavailableRow(consumer: PacNodeStatusConsumer, reason: string): Record<string, unknown> {
  return {
    consumer: consumer.id,
    node: consumer.node,
    lane: consumer.lane,
    repository: consumer.repo,
    namespace: consumer.namespace,
    pipeline: consumer.pipeline,
    argoApplication: consumer.argoApplication,
    ready: false,
    ciReady: false,
    argoReady: false,
    runtimeReady: false,
    diagnosticsReady: false,
    reason,
    firstBreak: { code: reason, phase: "node-status-remote-observe", valuesPrinted: false },
    valuesPrinted: false,
  };
}

function printProgress(
  options: PacNodeStatusCaptureOptions,
  status: string,
  elapsedMs: number,
  detail: { exitCode: number; firstBreak: string } | null,
): void {
  process.stderr.write(`${JSON.stringify({
    event: "platform-infra.pipelines-as-code.read.progress",
    target: options.target.id,
    consumer: "*",
    stage: "node-status-remote-observe",
    status,
    elapsedMs,
    timeoutMs: options.timeoutMs,
    configPath: options.configPath,
    ...(detail ?? {}),
    valuesPrinted: false,
  })}\n`);
}
