import { existsSync, readFileSync } from "node:fs";

export type CliChildJsonStdoutKind = "json" | "empty" | "dump-wrapper" | "ssh-stdout-truncated" | "ssh-truncation-summary" | "non-json";
export type CliChildJsonSource = "stdout" | "dump" | "artifact" | null;

export interface CliChildJsonArtifactFallback {
  path: string | null;
  nextCommand: string | null;
  read: () => CliChildJsonArtifactReadResult;
}

export type CliChildJsonArtifactReadResult =
  | { ok: true; value: Record<string, unknown>; path?: string | null; reason?: string | null }
  | { ok: false; reason: string; path?: string | null; error?: string | null };

export interface CliChildJsonResolution {
  parsed: Record<string, unknown> | null;
  source: CliChildJsonSource;
  diagnostics: Record<string, unknown>;
}

export function resolveCliChildJsonCommandResult(options: {
  result: { stdout: string; stderr?: string; exitCode?: number | null; timedOut?: boolean };
  requestedStdoutType: string;
  acceptParsed?: (value: Record<string, unknown>) => boolean;
  artifactFallback?: CliChildJsonArtifactFallback | null;
  forceArtifactFallbackReason?: string | null;
}): CliChildJsonResolution {
  return resolveCliChildJsonObject({
    stdout: options.result.stdout,
    stderr: options.result.stderr ?? "",
    requestedStdoutType: options.requestedStdoutType,
    acceptParsed: options.acceptParsed,
    artifactFallback: options.artifactFallback,
    forceArtifactFallbackReason: options.forceArtifactFallbackReason,
  });
}

export function resolveCliChildJsonObject(options: {
  stdout: string;
  stderr?: string;
  requestedStdoutType: string;
  acceptParsed?: (value: Record<string, unknown>) => boolean;
  artifactFallback?: CliChildJsonArtifactFallback | null;
  forceArtifactFallbackReason?: string | null;
}): CliChildJsonResolution {
  const stderr = options.stderr ?? "";
  const parsedStdout = parseStdoutCandidate(options.stdout, stderr);
  const accepted = parsedStdout.parsed !== null && (options.acceptParsed?.(parsedStdout.parsed) ?? true);
  const contractFallbackReason = parsedStdout.parsed !== null && !accepted ? "stdout-json-contract-invalid" : null;
  const fallbackReason = options.forceArtifactFallbackReason
    ?? contractFallbackReason
    ?? (parsedStdout.parsed === null ? fallbackReasonForStdout(parsedStdout.stdoutKind) : null);

  let parsed = contractFallbackReason === null ? parsedStdout.parsed : null;
  let source: CliChildJsonSource = contractFallbackReason === null ? parsedStdout.source : null;
  let artifactDiagnostics: Record<string, unknown> | null = null;
  if (fallbackReason !== null && options.artifactFallback) {
    const artifact = options.artifactFallback.read();
    artifactDiagnostics = {
      path: artifact.path ?? options.artifactFallback.path,
      requestedPath: options.artifactFallback.path,
      nextCommand: options.artifactFallback.nextCommand,
      ok: artifact.ok,
      reason: artifact.ok ? artifact.reason ?? fallbackReason : artifact.reason,
      error: artifact.ok ? null : artifact.error ?? null,
      valuesRedacted: true,
    };
    if (artifact.ok) {
      parsed = artifact.value;
      source = "artifact";
    }
  }

  return {
    parsed,
    source,
    diagnostics: {
      requestedStdoutType: options.requestedStdoutType,
      stdoutKind: parsedStdout.stdoutKind,
      source,
      fallbackReason,
      parsedFromStdout: parsedStdout.source === "stdout",
      parsedFromDump: parsedStdout.source === "dump",
      stdoutContractAccepted: accepted,
      dumpPath: parsedStdout.dumpPath,
      dumpReason: parsedStdout.dumpReason,
      dumpReadOk: parsedStdout.dumpReadOk,
      dumpJsonOk: parsedStdout.dumpJsonOk,
      sshTruncation: parsedStdout.sshTruncation,
      artifact: artifactDiagnostics,
      valuesRedacted: true,
    },
  };
}

function parseStdoutCandidate(stdout: string, stderr: string): {
  parsed: Record<string, unknown> | null;
  source: CliChildJsonSource;
  stdoutKind: CliChildJsonStdoutKind;
  dumpPath: string | null;
  dumpReason: string | null;
  dumpReadOk: boolean | null;
  dumpJsonOk: boolean | null;
  sshTruncation: Record<string, unknown> | null;
} {
  const trimmed = stdout.trim();
  const sshTruncation = parseSshTruncation(stdout, stderr);
  if (trimmed.length === 0) {
    return emptyCandidate("empty", sshTruncation);
  }
  if (trimmed.startsWith("UNIDESK_SSH_STDOUT_TRUNCATED ")) {
    const dumpPath = stringValue(parseMarkerJson(trimmed, "UNIDESK_SSH_STDOUT_TRUNCATED "), "dumpPath");
    return dumpPath ? parseDumpCandidate("ssh-stdout-truncated", dumpPath, "ssh-stdout-truncated", sshTruncation) : emptyCandidate("ssh-stdout-truncated", sshTruncation);
  }
  if (trimmed.startsWith("UNIDESK_SSH_TRUNCATION_SUMMARY ")) {
    const dumpPath = stdoutDumpPathFromSshSummary(parseMarkerJson(trimmed, "UNIDESK_SSH_TRUNCATION_SUMMARY "));
    return dumpPath ? parseDumpCandidate("ssh-truncation-summary", dumpPath, "ssh-truncation-summary", sshTruncation) : emptyCandidate("ssh-truncation-summary", sshTruncation);
  }

  const parsed = parseJsonObject(trimmed);
  if (parsed !== null) {
    const dumpPayload = cliDumpPayload(parsed);
    if (dumpPayload !== null) {
      return parseDumpCandidate("dump-wrapper", dumpPayload.path, dumpPayload.reason, sshTruncation);
    }
    return {
      parsed,
      source: "stdout",
      stdoutKind: "json",
      dumpPath: null,
      dumpReason: null,
      dumpReadOk: null,
      dumpJsonOk: null,
      sshTruncation,
    };
  }

  const summaryDumpPath = stdoutDumpPathFromSshSummary(sshTruncation);
  if (summaryDumpPath !== null) {
    return parseDumpCandidate("ssh-truncation-summary", summaryDumpPath, "ssh-truncation-summary", sshTruncation);
  }
  return emptyCandidate("non-json", sshTruncation);
}

function parseDumpCandidate(stdoutKind: CliChildJsonStdoutKind, dumpPath: string, reason: string | null, sshTruncation: Record<string, unknown> | null): {
  parsed: Record<string, unknown> | null;
  source: CliChildJsonSource;
  stdoutKind: CliChildJsonStdoutKind;
  dumpPath: string | null;
  dumpReason: string | null;
  dumpReadOk: boolean | null;
  dumpJsonOk: boolean | null;
  sshTruncation: Record<string, unknown> | null;
} {
  if (!existsSync(dumpPath)) {
    return {
      ...emptyCandidate(stdoutKind, sshTruncation),
      dumpPath,
      dumpReason: reason,
      dumpReadOk: false,
      dumpJsonOk: null,
    };
  }
  const parsed = parseJsonObject(readFileSync(dumpPath, "utf8"));
  return {
    parsed,
    source: parsed === null ? null : "dump",
    stdoutKind,
    dumpPath,
    dumpReason: reason,
    dumpReadOk: true,
    dumpJsonOk: parsed !== null,
    sshTruncation,
  };
}

function emptyCandidate(stdoutKind: CliChildJsonStdoutKind, sshTruncation: Record<string, unknown> | null) {
  return {
    parsed: null,
    source: null,
    stdoutKind,
    dumpPath: null,
    dumpReason: null,
    dumpReadOk: null,
    dumpJsonOk: null,
    sshTruncation,
  };
}

function fallbackReasonForStdout(kind: CliChildJsonStdoutKind): string {
  if (kind === "empty") return "stdout-empty";
  if (kind === "dump-wrapper") return "stdout-dump-wrapper-unreadable";
  if (kind === "ssh-stdout-truncated") return "stdout-ssh-truncated";
  if (kind === "ssh-truncation-summary") return "stdout-trans-truncated";
  return "stdout-not-json";
}

function cliDumpPayload(parsed: Record<string, unknown>): { path: string; reason: string | null } | null {
  for (const candidate of [parsed, record(parsed.data), record(parsed.error)]) {
    if (candidate.outputTruncated !== true) continue;
    const path = stringValue(record(candidate.dump), "path");
    if (path !== null) return { path, reason: stringValue(candidate, "reason") };
  }
  return null;
}

function parseSshTruncation(stdout: string, stderr: string): Record<string, unknown> | null {
  const text = `${stdout}\n${stderr}`;
  const markers = ["UNIDESK_SSH_TRUNCATION_SUMMARY ", "UNIDESK_SSH_STDOUT_TRUNCATED "];
  for (const marker of markers) {
    const lines = text.split(/\r?\n/u).filter((line) => line.trim().startsWith(marker));
    const last = lines.at(-1)?.trim();
    const parsed = last ? parseMarkerJson(last, marker) : null;
    if (parsed !== null) return parsed;
  }
  return null;
}

function parseMarkerJson(line: string, marker: string): Record<string, unknown> | null {
  if (!line.startsWith(marker)) return null;
  return parseJsonObject(line.slice(marker.length));
}

function stdoutDumpPathFromSshSummary(value: Record<string, unknown> | null): string | null {
  const root = record(value);
  const stdout = record(root.stdout);
  return stringValue(stdout, "dumpPath") ?? stringValue(root, "dumpPath");
}

function parseJsonObject(text: string): Record<string, unknown> | null {
  const trimmed = text.trim();
  if (trimmed.length === 0) return null;
  try {
    return record(JSON.parse(trimmed) as unknown);
  } catch {
    const objectText = firstJsonObjectText(trimmed);
    if (objectText === null) return null;
    try {
      return record(JSON.parse(objectText) as unknown);
    } catch {
      return null;
    }
  }
}

function firstJsonObjectText(text: string): string | null {
  const start = text.indexOf("{");
  if (start < 0) return null;
  let depth = 0;
  let inString = false;
  let escaped = false;
  for (let index = start; index < text.length; index += 1) {
    const char = text[index];
    if (inString) {
      if (escaped) escaped = false;
      else if (char === "\\") escaped = true;
      else if (char === "\"") inString = false;
      continue;
    }
    if (char === "\"") {
      inString = true;
      continue;
    }
    if (char === "{") depth += 1;
    else if (char === "}") {
      depth -= 1;
      if (depth === 0) return text.slice(start, index + 1);
    }
  }
  return null;
}

function record(value: unknown): Record<string, unknown> {
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
}

function stringValue(value: Record<string, unknown>, key: string): string | null {
  const item = value[key];
  return typeof item === "string" && item.length > 0 ? item : null;
}
