import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import {
  chmodSync,
  lstatSync,
  mkdirSync,
  mkdtempSync,
  readFileSync,
  readlinkSync,
  writeFileSync,
} from "node:fs";
import { homedir, tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "node:test";
import {
  aggregateExitCode,
  auditNode,
  auditNodes,
  convergeNode,
  convergeNodes,
  formatAuditReport,
} from "../../../lib/fleet/engine.mjs";
import { canonicalSourceMode } from "../../../lib/fleet/mode.mjs";
import { createLocalTransport } from "../../../lib/fleet/transport-local.mjs";
import { createSshTransport, SshTransportError } from "../../../lib/fleet/transport-ssh.mjs";

function sha256Hex(value) {
  return createHash("sha256").update(value).digest("hex");
}

function makeItem(overrides = {}) {
  return {
    id: "test:item",
    source: { kind: "repo", path: "modules/fleet/test/fixtures/engine-source.txt", ref: null },
    destination: "~/fleet-engine-test.txt",
    ownership: "user",
    strategy: "copy",
    mode: "converge",
    content_digest: sha256Hex("fleet engine fixture"),
    owner: "fleet",
    ...overrides,
  };
}

function fixtureRegistry() {
  return {
    hosts: [
      {
        name: "debian1",
        state: "reachable",
        ssh_alias: "debian1",
        access: {
          tailscale_ip: { target: "user@100.64.0.1", port: 2222, identity_file: "~/.ssh/id_ed25519_buildbox" },
          lan: null,
          tailscale_ssh: null,
        },
      },
      {
        name: "debian2",
        state: "unreachable",
        ssh_alias: "debian2",
        access: {
          tailscale_ip: null,
          lan: null,
          tailscale_ssh: null,
        },
      },
    ],
  };
}

function parseShqArgs(text) {
  const args = [];
  let current = "";
  let quote = null;
  for (let i = 0; i < text.length; i += 1) {
    const ch = text[i];
    if (quote === "'") {
      if (ch === "'") {
        if (text[i + 1] === "'") {
          current += "'";
          i += 1;
        } else {
          quote = null;
        }
      } else {
        current += ch;
      }
      continue;
    }
    if (ch === "'") {
      quote = "'";
      continue;
    }
    if (ch === " ") {
      if (current) {
        args.push(current);
        current = "";
      }
      continue;
    }
    current += ch;
  }
  if (current) args.push(current);
  return args;
}

function decodeBashInvocation(command) {
  const match = /^printf %s '([^']*)' \| base64 -d \| bash -s (.*)$/.exec(command);
  if (!match) return null;
  const script = Buffer.from(match[1], "base64").toString("utf8");
  return { script, args: parseShqArgs(match[2]) };
}

class SharedRemote {
  constructor() {
    this.files = new Map();
    this.units = new Map();
    this.history = [];
  }

  readDigest(path) {
    if (!this.files.has(path)) {
      return { absent: true };
    }
    return {
      absent: false,
      digest: sha256Hex(this.files.get(path)),
      ownership: "user",
      mode: "644",
      linkTarget: null,
    };
  }

  runBash(script, args) {
    if (script.includes("dest=$1") && script.includes("printf 'promoted")) {
      const [dest, staging, expected] = args;
      if (this.files.has(dest) && sha256Hex(this.files.get(dest)) === expected) {
        this.files.delete(staging);
        return { stdout: "noop\n", stderr: "", code: 0 };
      }
      if (!this.files.has(staging)) {
        return { stdout: "", stderr: "missing staging", code: 1 };
      }
      if (sha256Hex(this.files.get(staging)) !== expected) {
        this.files.delete(staging);
        return { stdout: "", stderr: "digest mismatch", code: 42 };
      }
      this.files.set(dest, this.files.get(staging));
      this.files.delete(staging);
      return { stdout: "promoted\n", stderr: "", code: 0 };
    }
    if (script.includes("__FLEET__:absent")) {
      const [path] = args;
      const info = this.readDigest(path);
      if (info.absent) {
        return { stdout: "__FLEET__:absent\n", stderr: "", code: 0 };
      }
      return {
        stdout: `exists=1\ndigest=${info.digest}\nownership=${info.ownership}\nmode=${info.mode}\nlink=\n`,
        stderr: "",
        code: 0,
      };
    }
    if (script.includes('rm -f "$1"')) {
      this.files.delete(args[0]);
      return { stdout: "", stderr: "", code: 0 };
    }
    return { stdout: "", stderr: `unhandled bash script`, code: 127 };
  }

  runSsh(command) {
    this.history.push(command);
    const bash = decodeBashInvocation(command);
    if (bash) {
      return this.runBash(bash.script, bash.args);
    }
    if (/^mkdir -p /.test(command)) {
      return { stdout: "", stderr: "", code: 0 };
    }
    if (/^systemctl --user is-enabled /.test(command)) {
      const unit = this.units.get(command.match(/'([^']+)'/)?.[1] ?? "") ?? { enabled: false, active: false };
      return unit.enabled
        ? { stdout: "enabled\n", stderr: "", code: 0 }
        : { stdout: "disabled\n", stderr: "", code: 1 };
    }
    if (/^systemctl --user is-active /.test(command)) {
      const unit = this.units.get(command.match(/'([^']+)'/)?.[1] ?? "") ?? { enabled: false, active: false };
      return unit.active
        ? { stdout: "active\n", stderr: "", code: 0 }
        : { stdout: "inactive\n", stderr: "", code: 3 };
    }
    if (command.includes("git -C")) {
      return { stdout: "", stderr: "", code: 0 };
    }
    return { stdout: "", stderr: `unhandled ssh command: ${command}`, code: 127 };
  }

  exec(argv) {
    if (argv[0] === "ssh") {
      const command = argv[argv.length - 1];
      const result = this.runSsh(command);
      return Promise.resolve(result);
    }
    if (argv[0] === "scp") {
      const remotePath = argv[argv.length - 1].split(":")[1];
      const localPath = argv[argv.length - 2];
      this.files.set(remotePath, readFileSync(localPath));
      return Promise.resolve({ stdout: "", stderr: "", code: 0 });
    }
    return Promise.resolve({ stdout: "", stderr: `unhandled exec ${argv.join(" ")}`, code: 127 });
  }
}

function setupFixtureHome() {
  const home = mkdtempSync(join(tmpdir(), "fleet-engine-"));
  const sourceDir = join(home, "modules/fleet/test/fixtures");
  mkdirSync(sourceDir, { recursive: true });
  writeFileSync(join(sourceDir, "engine-source.txt"), "fleet engine fixture", { mode: 0o644 });
  return home;
}

function comparableAudit(report) {
  return {
    ok: report.ok,
    drift: report.drift,
    unreachable: report.unreachable,
    delegated: report.delegated,
  };
}

test("local and ssh transports produce equivalent audit reports for the same items", async () => {
  const home = setupFixtureHome();
  const remote = new SharedRemote();
  const content = Buffer.from("fleet engine fixture");
  const item = makeItem({
    destination: "~/fleet-engine-test.txt",
    content_digest: sha256Hex(content),
  });

  const local = createLocalTransport({ home });
  await local.write(item, content);
  chmodSync(join(home, "fleet-engine-test.txt"), 0o644);

  remote.files.set("/home/user/fleet-engine-test.txt", content);

  const localReport = await auditNode("workstation", [item], local, { repoRoot: home });
  const sshReport = await auditNode(
    "debian1",
    [{ ...item, destination: "/home/user/fleet-engine-test.txt" }],
    createSshTransport({
      hostRef: "debian1",
      registry: fixtureRegistry(),
      exec: (argv) => remote.exec(argv),
    }),
    { repoRoot: home },
  );

  assert.equal(localReport.ok, true);
  assert.equal(sshReport.ok, true);
  assert.deepEqual(comparableAudit(localReport), comparableAudit(sshReport));
});

test("an empty deploy root resolves deploy sources from the canonical clone", async () => {
  const item = makeItem({
    source: { kind: "deploy", path: "modules/workstation/claude/bin", ref: null },
    destination: "~/.claude/bin",
    strategy: "symlink",
  });
  const transport = {
    async read() {
      return { exists: false, ownership: "user" };
    },
  };

  const report = await auditNode("workstation", [item], transport, { deployRoot: "" });
  const linkDrift = report.drift.find((entry) => entry.field === "link_target");

  assert.equal(
    linkDrift.want,
    join(homedir(), ".local/share/overdeck/deploy/modules/workstation/claude/bin"),
  );
});

test("a drifted item converges and the embedded final audit is clean", async () => {
  const home = setupFixtureHome();
  const transport = createLocalTransport({ home });
  const content = Buffer.from("fleet engine fixture");
  const item = makeItem({
    destination: "~/drift-me.txt",
    content_digest: sha256Hex(content),
  });

  writeFileSync(join(home, "drift-me.txt"), "stale bytes");

  const before = await auditNode("workstation", [item], transport, { repoRoot: home });
  assert.equal(before.ok, false);
  assert.ok(before.drift.some((entry) => entry.id === item.id));

  const result = await convergeNode("workstation", [item], transport, { repoRoot: home, home });
  assert.deepEqual(result.failed, []);
  assert.ok(result.changed.includes(item.id));
  assert.equal(result.finalAudit.ok, true);
  assert.equal(readFileSync(join(home, "drift-me.txt"), "utf8"), content.toString("utf8"));
});

test("a node transport error does not discard healthy peer audit reports", async () => {
  const home = setupFixtureHome();
  const content = Buffer.from("fleet engine fixture");
  const item = makeItem({
    destination: "/home/user/fleet-engine-test.txt",
    content_digest: sha256Hex(content),
  });
  const goodTransport = {
    name: "local",
    async read() {
      return {
        exists: true,
        digest: item.content_digest,
        ownership: "user",
        mode: "644",
        linkTarget: null,
      };
    },
    async write() {},
    async unitState() {
      return { enabled: true, active: true };
    },
    async gitRef() {
      return null;
    },
  };
  const badTransport = {
    ...goodTransport,
    async read() {
      throw new Error("permission denied");
    },
  };

  const reports = await auditNodes(
    [
      { nodeName: "bad", items: [item], transport: badTransport },
      { nodeName: "good", items: [item], transport: goodTransport },
    ],
    { repoRoot: home, log: () => {} },
  );

  assert.equal(reports.length, 2);
  assert.equal(reports[0].ok, false);
  assert.equal(reports[0].node, "bad");
  assert.equal(reports[1].ok, true);
  assert.equal(reports[1].node, "good");
  assert.equal(aggregateExitCode(reports), 1);
});

test("a thrown audit prints the failure reason and leaves the healthy peer OK", async () => {
  const home = setupFixtureHome();
  const content = Buffer.from("fleet engine fixture");
  const item = makeItem({
    destination: "/home/user/fleet-engine-test.txt",
    content_digest: sha256Hex(content),
  });
  const goodTransport = {
    name: "local",
    async read() {
      return {
        exists: true,
        digest: item.content_digest,
        ownership: "user",
        mode: "644",
        linkTarget: null,
      };
    },
    async write() {},
    async unitState() {
      return { enabled: true, active: true };
    },
    async gitRef() {
      return null;
    },
  };
  const badTransport = {
    ...goodTransport,
    async read() {
      throw new Error("permission denied");
    },
  };

  const lines = [];
  const reports = await auditNodes(
    [
      { nodeName: "bad", items: [item], transport: badTransport },
      { nodeName: "good", items: [item], transport: goodTransport },
    ],
    { repoRoot: home, log: (line) => lines.push(line) },
  );

  assert.equal(reports[0].error, "permission denied");
  assert.deepEqual(lines, ["ERROR bad permission denied", "OK good"]);
  assert.equal(aggregateExitCode(reports), 1);
});

test("formatAuditReport never renders OK for a failed report without drift", () => {
  assert.deepEqual(
    formatAuditReport({
      node: "alpha",
      ok: false,
      drift: [],
      unreachable: false,
      delegated: null,
      error: "ssh exited 255",
    }),
    ["ERROR alpha ssh exited 255"],
  );
  assert.deepEqual(
    formatAuditReport({
      node: "alpha",
      ok: false,
      drift: [],
      unreachable: false,
      delegated: { ok: false, lines: [{ level: "WARN", msg: "noted" }] },
    }),
    ["ERROR alpha audit failed"],
  );
  assert.deepEqual(
    formatAuditReport({ node: "alpha", ok: true, drift: [], unreachable: false, delegated: null }),
    ["OK alpha"],
  );
});

test("a node transport error does not discard healthy peer converge reports", async () => {
  const home = setupFixtureHome();
  const content = Buffer.from("fleet engine fixture");
  const item = makeItem({
    destination: "/home/user/fleet-engine-test.txt",
    content_digest: sha256Hex(content),
  });
  const goodTransport = {
    name: "local",
    async read() {
      return {
        exists: true,
        digest: item.content_digest,
        ownership: "user",
        mode: "644",
        linkTarget: null,
      };
    },
    async write() {},
    async unitState() {
      return { enabled: true, active: true };
    },
    async gitRef() {
      return null;
    },
  };
  const badTransport = {
    ...goodTransport,
    async read() {
      throw new Error("permission denied");
    },
  };

  const reports = await convergeNodes(
    [
      { nodeName: "bad", items: [item], transport: badTransport },
      { nodeName: "good", items: [item], transport: goodTransport },
    ],
    { repoRoot: home, log: () => {} },
  );

  assert.equal(reports.length, 2);
  assert.equal(reports[0].failed.length, 1);
  assert.equal(reports[0].finalAudit.ok, false);
  assert.equal(reports[0].node, "bad");
  assert.equal(reports[1].finalAudit.ok, true);
  assert.equal(reports[1].node, "good");
  assert.equal(aggregateExitCode(reports), 1);
});

test("a thrown converge prints the failure reason and leaves the healthy peer OK", async () => {
  const home = setupFixtureHome();
  const content = Buffer.from("fleet engine fixture");
  const item = makeItem({
    destination: "/home/user/fleet-engine-test.txt",
    content_digest: sha256Hex(content),
  });
  const goodTransport = {
    name: "local",
    async read() {
      return {
        exists: true,
        digest: item.content_digest,
        ownership: "user",
        mode: "644",
        linkTarget: null,
      };
    },
    async write() {},
    async unitState() {
      return { enabled: true, active: true };
    },
    async gitRef() {
      return null;
    },
  };
  const badTransport = {
    ...goodTransport,
    async read() {
      throw new Error("permission denied");
    },
  };

  const lines = [];
  const reports = await convergeNodes(
    [
      { nodeName: "bad", items: [item], transport: badTransport },
      { nodeName: "good", items: [item], transport: goodTransport },
    ],
    { repoRoot: home, log: (line) => lines.push(line) },
  );

  assert.equal(reports[0].finalAudit.error, "permission denied");
  assert.deepEqual(lines, ["ERROR bad permission denied", "OK good"]);
  assert.equal(aggregateExitCode(reports), 1);
});

test("an unreachable node reports unreachable:true while a healthy peer still audits", async () => {
  const home = setupFixtureHome();
  const content = Buffer.from("fleet engine fixture");
  const item = makeItem({
    destination: "/home/user/fleet-engine-test.txt",
    content_digest: sha256Hex(content),
  });
  const healthyRemote = new SharedRemote();
  healthyRemote.files.set(item.destination, content);

  const unreachableTransport = {
    name: "ssh",
    async read() {
      throw new SshTransportError("debian2", "Connection refused");
    },
    async write() {
      throw new Error("unexpected write");
    },
    async unitState() {
      throw new Error("unexpected unitState");
    },
    async gitRef() {
      throw new Error("unexpected gitRef");
    },
  };

  const reports = await auditNodes(
    [
      { nodeName: "debian2", items: [item], transport: unreachableTransport },
      {
        nodeName: "debian1",
        items: [item],
        transport: createSshTransport({
          hostRef: "debian1",
          registry: fixtureRegistry(),
          exec: (argv) => healthyRemote.exec(argv),
        }),
      },
    ],
    { repoRoot: home, log: () => {} },
  );

  assert.equal(reports[0].unreachable, true);
  assert.equal(reports[1].ok, true);
  assert.equal(aggregateExitCode(reports), 1);
});

test("agent-home items are realized locally and delegated over ssh", async () => {
  const home = setupFixtureHome();
  mkdirSync(join(home, "modules/workstation/claude/hooks"), { recursive: true });
  const transport = createLocalTransport({ home });
  const fleetItem = makeItem({
    id: "fleet:copy",
    destination: "~/fleet-owned.txt",
  });
  const delegatedItem = makeItem({
    id: "agent-home:claude:hooks",
    owner: "remote-seat-provision",
    destination: "~/agent-home-hooks",
    strategy: "symlink",
    source: { kind: "repo", path: "modules/workstation/claude/hooks", ref: null },
    content_digest: sha256Hex("delegated"),
  });

  let provisionCalled = false;
  const report = await auditNode(
    "workstation",
    [fleetItem, delegatedItem],
    transport,
    {
      repoRoot: home,
      home,
      deps: {
        auditHostSeat: () => [{ level: "PASS", msg: "delegated audit" }],
        provisionHost: () => {
          provisionCalled = true;
          return { ok: true };
        },
      },
    },
  );

  assert.ok(report.delegated);
  assert.equal(report.delegated.owner, "remote-seat-provision");
  assert.deepEqual(report.delegated.itemIds, [delegatedItem.id]);

  const converge = await convergeNode("workstation", [fleetItem, delegatedItem], transport, {
    repoRoot: home,
    home,
    deps: {
      auditHostSeat: () => [{ level: "PASS", msg: "delegated audit" }],
      provisionHost: () => {
        provisionCalled = true;
        return { ok: true };
      },
    },
  });

  assert.equal(provisionCalled, false);
  assert.ok(converge.changed.includes(delegatedItem.id));
  assert.deepEqual(
    converge.failed.filter((entry) => entry.id === delegatedItem.id),
    [],
  );
  assert.deepEqual(converge.finalAudit.delegated.drift, []);
  assert.equal(
    readlinkSync(join(home, "agent-home-hooks")),
    join(home, "modules/workstation/claude/hooks"),
  );

  const again = await convergeNode("workstation", [fleetItem, delegatedItem], transport, {
    repoRoot: home,
    home,
    deps: {
      auditHostSeat: () => [{ level: "PASS", msg: "delegated audit" }],
      provisionHost: () => {
        provisionCalled = true;
        return { ok: true };
      },
    },
  });

  assert.ok(!again.changed.includes(delegatedItem.id));
});

test("remote seat delegation receives explicit caller project root", async () => {
  const projectRoot = "/tmp/caller-checkout";
  let received;
  const report = await auditNode(
    "debian1",
    [makeItem({ owner: "remote-seat-provision" })],
    { name: "ssh" },
    {
      seatOpts: { projectRoot },
      deps: {
        loadRegistry: fixtureRegistry,
        loadRemoteConfig: () => ({ remote_root: "/tmp/remote" }),
        auditHostSeat: (_cfg, opts) => {
          received = opts;
          return [{ level: "PASS", msg: "delegated audit" }];
        },
      },
    },
  );

  assert.equal(report.delegated.ok, true);
  assert.deepEqual(received, { projectRoot });
});

test("parallel audit output stays declaration-ordered", async () => {
  const home = setupFixtureHome();
  const content = Buffer.from("fleet engine fixture");
  const item = makeItem({
    destination: "/home/user/fleet-engine-test.txt",
    content_digest: sha256Hex(content),
  });

  const delays = new Map([
    ["alpha", 30],
    ["beta", 0],
  ]);
  const lines = [];

  const makeTransport = (name) => {
    const remote = new SharedRemote();
    remote.files.set(item.destination, content);
    return createSshTransport({
      hostRef: "debian1",
      registry: fixtureRegistry(),
      exec: async (argv) => {
        await new Promise((resolve) => setTimeout(resolve, delays.get(name) ?? 0));
        return remote.exec(argv);
      },
    });
  };

  await auditNodes(
    [
      { nodeName: "alpha", items: [item], transport: makeTransport("alpha") },
      { nodeName: "beta", items: [item], transport: makeTransport("beta") },
    ],
    { repoRoot: home, log: (line) => lines.push(line) },
  );

  assert.deepEqual(
    lines.filter((line) => line.startsWith("OK ")),
    ["OK alpha", "OK beta"],
  );
});

test("canonical source mode ignores the working-tree group and other bits", () => {
  const dir = mkdtempSync(join(tmpdir(), "fleet-mode-"));
  const plain = join(dir, "plain.conf");
  const exec = join(dir, "runner.sh");
  writeFileSync(plain, "plain");
  writeFileSync(exec, "#!/bin/sh\n");

  chmodSync(plain, 0o664);
  assert.equal(canonicalSourceMode(lstatSync(plain)), "644");
  chmodSync(plain, 0o600);
  assert.equal(canonicalSourceMode(lstatSync(plain)), "644");
  chmodSync(plain, 0o666);
  assert.equal(canonicalSourceMode(lstatSync(plain)), "644");

  chmodSync(exec, 0o775);
  assert.equal(canonicalSourceMode(lstatSync(exec)), "755");
  chmodSync(exec, 0o664 | 0o100);
  assert.equal(canonicalSourceMode(lstatSync(exec)), "755");
  chmodSync(exec, 0o701);
  assert.equal(canonicalSourceMode(lstatSync(exec)), "755");

  assert.equal(canonicalSourceMode(lstatSync(dir)), "755");
});

test("audit expects canonical modes regardless of the source umask bits", async () => {
  const home = setupFixtureHome();
  const source = join(home, "modules/fleet/test/fixtures/engine-source.txt");
  const content = Buffer.from("fleet engine fixture");
  const transport = createLocalTransport({ home });

  const item = makeItem({ destination: "~/fleet-engine-test.txt", content_digest: sha256Hex(content) });
  await transport.write(item, content);
  const dest = join(home, "fleet-engine-test.txt");

  chmodSync(source, 0o664);
  chmodSync(dest, 0o644);
  assert.equal((await auditNode("workstation", [item], transport, { repoRoot: home, home })).ok, true);

  chmodSync(dest, 0o664);
  assert.deepEqual(
    (await auditNode("workstation", [item], transport, { repoRoot: home, home })).drift.filter(
      (entry) => entry.field === "mode",
    ),
    [{ id: item.id, field: "mode", want: "644", have: "664" }],
  );

  chmodSync(source, 0o664 | 0o100);
  chmodSync(dest, 0o755);
  assert.equal((await auditNode("workstation", [item], transport, { repoRoot: home, home })).ok, true);

  chmodSync(source, 0o775);
  chmodSync(dest, 0o775);
  assert.deepEqual(
    (await auditNode("workstation", [item], transport, { repoRoot: home, home })).drift.filter(
      (entry) => entry.field === "mode",
    ),
    [{ id: item.id, field: "mode", want: "755", have: "775" }],
  );
});

test("converge installs the canonical mode, not the working-tree mode", async () => {
  const home = setupFixtureHome();
  const source = join(home, "modules/fleet/test/fixtures/engine-source.txt");
  chmodSync(source, 0o664);
  const transport = createLocalTransport({ home });
  const item = makeItem({
    destination: "~/fleet-engine-converge.txt",
    content_digest: sha256Hex("fleet engine fixture"),
  });

  await convergeNode("workstation", [item], transport, { repoRoot: home, home });
  assert.equal(lstatSync(join(home, "fleet-engine-converge.txt")).mode & 0o777, 0o644);

  chmodSync(source, 0o775);
  await convergeNode("workstation", [item], transport, { repoRoot: home, home });
  assert.equal(lstatSync(join(home, "fleet-engine-converge.txt")).mode & 0o777, 0o755);
});
