import assert from "node:assert/strict";
import { mkdtemp, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "bun:test";

import { resolveCliChildJsonCommandResult, resolveCliChildJsonObject } from "./cli-child-json-recovery";

test("child JSON recovery reads full JSON from CLI stdout dump wrapper", async () => {
  const dir = await mkdtemp(join(tmpdir(), "unidesk-cli-child-json-dump-"));
  const dumpPath = join(dir, "dump.json");
  await writeFile(dumpPath, JSON.stringify({ ok: true, data: { contract: "full-json" } }) + "\n");

  const wrapper = {
    ok: true,
    command: "fixture",
    data: {
      outputTruncated: true,
      reason: "stdout-json-bytes-exceeded-threshold",
      dump: { path: dumpPath },
    },
  };
  const resolved = resolveCliChildJsonObject({
    stdout: JSON.stringify(wrapper),
    requestedStdoutType: "fixture JSON",
  });

  assert.equal(resolved.source, "dump");
  assert.equal(resolved.parsed?.ok, true);
  assert.deepEqual(resolved.parsed?.data, { contract: "full-json" });
  assert.equal(resolved.diagnostics.stdoutKind, "dump-wrapper");
  assert.equal(resolved.diagnostics.dumpPath, dumpPath);
  assert.equal(resolved.diagnostics.dumpReadOk, true);
  assert.equal(resolved.diagnostics.dumpJsonOk, true);
});

test("child JSON recovery command-result helper keeps dump diagnostics", async () => {
  const dir = await mkdtemp(join(tmpdir(), "unidesk-cli-child-json-result-dump-"));
  const dumpPath = join(dir, "dump.json");
  await writeFile(dumpPath, JSON.stringify({ ok: true, verified: true, bytes: 123 }) + "\n");
  const resolved = resolveCliChildJsonCommandResult({
    result: {
      stdout: JSON.stringify({
        ok: true,
        data: {
          outputTruncated: true,
          reason: "stdout-json-bytes-exceeded-threshold",
          dump: { path: dumpPath },
        },
      }),
      stderr: "",
      exitCode: 0,
      timedOut: false,
    },
    requestedStdoutType: "trans download verification JSON",
    acceptParsed: (value) => value.ok === true && value.verified === true,
  });

  assert.equal(resolved.source, "dump");
  assert.equal(resolved.parsed?.verified, true);
  assert.equal(resolved.diagnostics.requestedStdoutType, "trans download verification JSON");
  assert.equal(resolved.diagnostics.dumpPath, dumpPath);
});

test("child JSON recovery falls back to artifact after trans truncation summary", () => {
  const summary = {
    code: "ssh-truncation-summary",
    exitCode: 0,
    timedOut: false,
    stdout: {
      stream: "stdout",
      thresholdBytes: 10240,
      observedBytesAtTruncation: 165000,
      forwardedBytes: 10240,
      dumpPath: null,
      dumpError: null,
    },
  };
  const resolved = resolveCliChildJsonObject({
    stdout: `UNIDESK_SSH_TRUNCATION_SUMMARY ${JSON.stringify(summary)}\n`,
    requestedStdoutType: "web-probe observe analyze compact JSON",
    artifactFallback: {
      path: "analysis/report.json",
      nextCommand: "bun scripts/cli.ts web-probe observe collect webobs-fixture --view files --file analysis/report.json",
      read: () => ({ ok: true, value: { ok: true, reportJsonPath: "analysis/report.json" }, reason: "analysis-artifact-contract" }),
    },
  });

  assert.equal(resolved.source, "artifact");
  assert.equal(resolved.parsed?.ok, true);
  assert.equal(resolved.diagnostics.stdoutKind, "ssh-truncation-summary");
  assert.equal(resolved.diagnostics.fallbackReason, "stdout-trans-truncated");
  const artifact = resolved.diagnostics.artifact as Record<string, unknown>;
  assert.equal(artifact.ok, true);
  assert.equal(artifact.nextCommand, "bun scripts/cli.ts web-probe observe collect webobs-fixture --view files --file analysis/report.json");
});

test("child JSON recovery falls back to artifact when stdout is not JSON", () => {
  const resolved = resolveCliChildJsonObject({
    stdout: "remote helper wrote a bounded text summary instead of JSON",
    requestedStdoutType: "web-probe observe analyze compact JSON",
    artifactFallback: {
      path: "analysis/report.json",
      nextCommand: "collect report",
      read: () => ({ ok: true, value: { ok: true, counts: { network: 9 }, reportJsonPath: "analysis/report.json" } }),
    },
  });

  assert.equal(resolved.source, "artifact");
  assert.equal(resolved.parsed?.ok, true);
  assert.deepEqual(resolved.parsed?.counts, { network: 9 });
  assert.equal(resolved.diagnostics.stdoutKind, "non-json");
  assert.equal(resolved.diagnostics.fallbackReason, "stdout-not-json");
});

test("child JSON recovery rejects ok-only stdout contract and uses artifact", () => {
  const resolved = resolveCliChildJsonObject({
    stdout: JSON.stringify({ ok: true }),
    requestedStdoutType: "web-probe observe analyze compact JSON",
    acceptParsed: (value) => typeof value.reportJsonPath === "string" || typeof value.counts === "object",
    artifactFallback: {
      path: "analysis/report.json",
      nextCommand: "collect report",
      read: () => ({ ok: true, value: { ok: true, reportJsonPath: "analysis/report.json", counts: { network: 9 } } }),
    },
  });

  assert.equal(resolved.source, "artifact");
  assert.equal(resolved.parsed?.reportJsonPath, "analysis/report.json");
  assert.deepEqual(resolved.parsed?.counts, { network: 9 });
  assert.equal(resolved.diagnostics.stdoutKind, "json");
  assert.equal(resolved.diagnostics.fallbackReason, "stdout-json-contract-invalid");
  assert.equal(resolved.diagnostics.stdoutContractAccepted, false);
});

test("child JSON recovery drops invalid ok-only stdout when artifact fallback is missing", () => {
  const resolved = resolveCliChildJsonObject({
    stdout: JSON.stringify({ ok: true }),
    requestedStdoutType: "web-probe observe analyze compact JSON",
    acceptParsed: (value) => typeof value.reportJsonPath === "string" || typeof value.counts === "object",
    artifactFallback: {
      path: "analysis/report.json",
      nextCommand: "collect report",
      read: () => ({ ok: false, reason: "artifact-missing", path: "analysis/report.json" }),
    },
  });

  assert.equal(resolved.parsed, null);
  assert.equal(resolved.source, null);
  assert.equal(resolved.diagnostics.stdoutKind, "json");
  assert.equal(resolved.diagnostics.fallbackReason, "stdout-json-contract-invalid");
  assert.equal(resolved.diagnostics.stdoutContractAccepted, false);
  const artifact = resolved.diagnostics.artifact as Record<string, unknown>;
  assert.equal(artifact.ok, false);
  assert.equal(artifact.reason, "artifact-missing");
  assert.equal(artifact.path, "analysis/report.json");
  assert.equal(artifact.nextCommand, "collect report");
});

test("child JSON recovery reports artifact fallback failure when stdout is unusable and artifact is missing", () => {
  const resolved = resolveCliChildJsonObject({
    stdout: "not json",
    requestedStdoutType: "web-probe observe analyze compact JSON",
    artifactFallback: {
      path: "analysis/report.json",
      nextCommand: "collect report",
      read: () => ({ ok: false, reason: "artifact-missing", path: "analysis/report.json" }),
    },
  });

  assert.equal(resolved.parsed, null);
  assert.equal(resolved.source, null);
  assert.equal(resolved.diagnostics.stdoutKind, "non-json");
  assert.equal(resolved.diagnostics.fallbackReason, "stdout-not-json");
  const artifact = resolved.diagnostics.artifact as Record<string, unknown>;
  assert.equal(artifact.ok, false);
  assert.equal(artifact.reason, "artifact-missing");
  assert.equal(artifact.path, "analysis/report.json");
  assert.equal(artifact.nextCommand, "collect report");
});
