import assert from "node:assert/strict";
import test from "node:test";

import {
  assertReadonlyKubectlArgs,
  buildDevRuntimeHotfixAuditReport,
  parseArgs
} from "./dev-runtime-hotfix-audit.mjs";

test("plan mode emits a bounded read-only plan and runs no kubectl commands", async () => {
  let commandCount = 0;
  const report = await buildDevRuntimeHotfixAuditReport(parseArgs(["--plan"]), {
    runCommand: async () => {
      commandCount += 1;
      throw new Error("plan mode must not execute commands");
    }
  });

  assert.equal(commandCount, 0);
  assert.equal(report.mode, "plan");
  assert.equal(report.safety.k8sWriteAttempted, false);
  assert.deepEqual(report.classifications, [
    "source-artifact-expected",
    "unknown-needs-manual-readonly-check"
  ]);
  assert.ok(report.commandPlan.readonly.every((command) => command.includes("KUBECONFIG=/etc/rancher/k3s/k3s.yaml")));
  assert.ok(report.commandPlan.forbiddenDefaultOperations.some((command) => command.includes("kubectl patch")));
});

test("collect-readonly classifies active ConfigMap mount and pod marker without write verbs", async () => {
  const commands = [];
  const report = await buildDevRuntimeHotfixAuditReport(parseArgs(["--collect-readonly"]), {
    runCommand: async ({ args, displayCommand }) => {
      commands.push(displayCommand);
      return fixtureResult(args);
    }
  });

  assert.equal(report.mode, "collect-readonly");
  assert.equal(report.safety.k8sWriteAttempted, false);
  assert.ok(report.classifications.includes("hotfix-configmap-present"));
  assert.ok(report.classifications.includes("deployment-mounts-hotfix"));
  assert.ok(report.classifications.includes("pod-loads-hotfix-marker"));
  assert.ok(report.classifications.includes("rollback-required"));
  assert.equal(report.conclusion.status, "hotfix-detected");
  assert.ok(commands.length > 0);
  assertNoWriteVerbs(commands);
});

test("collect-readonly reports no-hotfix-detected when ConfigMap and mount are absent", async () => {
  const report = await buildDevRuntimeHotfixAuditReport(parseArgs(["--collect-readonly"]), {
    runCommand: async ({ args }) => fixtureResult(args, { noHotfix: true })
  });

  assert.deepEqual(report.classifications, [
    "source-artifact-expected",
    "no-hotfix-detected"
  ]);
  assert.equal(report.conclusion.status, "clear");
});

test("collect-readonly keeps unknown classification when D601 node cannot be proven", async () => {
  const report = await buildDevRuntimeHotfixAuditReport(parseArgs(["--collect-readonly"]), {
    runCommand: async ({ args }) => {
      if (args.includes("nodes")) {
        return { ok: false, exitCode: 1, stdout: "", stderr: "unable to connect to the server" };
      }
      return fixtureResult(args);
    }
  });

  assert.ok(report.classifications.includes("unknown-needs-manual-readonly-check"));
  assert.notEqual(report.conclusion.status, "clear");
});

test("kubectl write verbs are rejected by the audit helper", () => {
  assert.throws(() => assertReadonlyKubectlArgs(["-n", "hwlab-dev", "patch", "deployment", "hwlab-cloud-api"]), /forbidden/u);
  assert.throws(() => assertReadonlyKubectlArgs(["-n", "hwlab-dev", "rollout", "restart", "deployment", "hwlab-cloud-api"]), /forbidden/u);
});

function fixtureResult(args, options = {}) {
  const text = args.join(" ");
  if (text.includes("get nodes")) {
    return okJson({ items: [{ metadata: { name: "d601" } }] });
  }
  if (text.includes("get configmap")) {
    if (options.noHotfix) {
      return { ok: false, exitCode: 1, stdout: "", stderr: "Error from server (NotFound): configmaps \"hwlab-cloud-api-code-agent-hotfix\" not found" };
    }
    return okJson({ data: { "code-agent-chat.ts": "// runtime-hotfix-pc-gateway-shell" } });
  }
  if (text.includes("get deployment")) {
    return okJson(deploymentFixture({ noHotfix: options.noHotfix }));
  }
  if (text.includes("get pods")) {
    return okJson({
      items: [{
        metadata: { name: "hwlab-cloud-api-abc", creationTimestamp: "2026-05-24T00:00:00Z" },
        status: { phase: "Running" },
        spec: { nodeName: "d601" }
      }]
    });
  }
  if (text.includes("exec") && text.includes("grep")) {
    return options.noHotfix
      ? { ok: false, exitCode: 1, stdout: "", stderr: "" }
      : { ok: true, exitCode: 0, stdout: "1:runtime-hotfix-pc-gateway-shell\n", stderr: "" };
  }
  if (text.includes("exec") && text.includes("node --check")) {
    return { ok: true, exitCode: 0, stdout: "", stderr: "" };
  }
  throw new Error(`unexpected fixture command: ${text}`);
}

function deploymentFixture({ noHotfix = false } = {}) {
  return {
    metadata: { generation: 9 },
    spec: {
      template: {
        metadata: {
          annotations: noHotfix ? {} : {
            "hwlab.pikastech.local/pc-gateway-shell-hotfix": "runtime-hotfix"
          }
        },
        spec: {
          volumes: noHotfix ? [] : [{
            name: "code-agent-hotfix",
            configMap: {
              name: "hwlab-cloud-api-code-agent-hotfix",
              items: [{ key: "code-agent-chat.ts", path: "code-agent-chat.ts" }]
            }
          }],
          containers: [{
            name: "hwlab-cloud-api",
            image: "127.0.0.1:5000/hwlab/hwlab-cloud-api:dev",
            volumeMounts: noHotfix ? [] : [{
              name: "code-agent-hotfix",
              mountPath: "/app/internal/cloud/code-agent-chat.ts",
              subPath: "code-agent-chat.ts",
              readOnly: true
            }]
          }]
        }
      }
    }
  };
}

function okJson(value) {
  return { ok: true, exitCode: 0, stdout: JSON.stringify(value), stderr: "" };
}

function assertNoWriteVerbs(commands) {
  const forbidden = /\bkubectl\s+(?:apply|patch|rollout|delete|create|replace|scale|set|edit|annotate|label)\b/u;
  for (const command of commands) {
    assert.doesNotMatch(command, forbidden, command);
  }
}
