import { Command } from "commander";
import { describe, expect, it, vi } from "vitest";
import { registerDnsCli } from "./dns-cli.js";
import { parseCanvasSnapshotPayload } from "./nodes-canvas.js";
import { parseByteSize } from "./parse-bytes.js";
import { parseDurationMs } from "./parse-duration.js";
import { shouldSkipRespawnForArgv } from "./respawn-policy.js";
import { waitForever } from "./wait.js";

describe("waitForever", () => {
  it("creates an unref'ed interval and returns a pending promise", () => {
    const setIntervalSpy = vi.spyOn(global, "setInterval");
    const promise = waitForever();
    expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 1_000_000);
    expect(promise).toBeInstanceOf(Promise);
    setIntervalSpy.mockRestore();
  });
});

describe("shouldSkipRespawnForArgv", () => {
  it.each([
    { argv: ["node", "openclaw", "--help"] },
    { argv: ["node", "openclaw", "-V"] },
  ] as const)("skips respawn for argv %j", ({ argv }) => {
    expect(shouldSkipRespawnForArgv([...argv]), argv.join(" ")).toBe(true);
  });

  it("keeps respawn path for normal commands", () => {
    expect(shouldSkipRespawnForArgv(["node", "openclaw", "status"])).toBe(false);
  });
});

describe("nodes canvas helpers", () => {
  it("parses canvas.snapshot payload", () => {
    expect(parseCanvasSnapshotPayload({ format: "png", base64: "aGk=" })).toEqual({
      format: "png",
      base64: "aGk=",
    });
  });

  it("rejects invalid canvas.snapshot payload", () => {
    expect(() => parseCanvasSnapshotPayload({ format: "png" })).toThrow(
      /invalid canvas\.snapshot payload/i,
    );
  });
});

describe("dns cli", () => {
  it("prints setup info (no apply)", async () => {
    const log = vi.spyOn(console, "log").mockImplementation(() => {});
    try {
      const program = new Command();
      registerDnsCli(program);
      await program.parseAsync(["dns", "setup", "--domain", "openclaw.internal"], { from: "user" });
      const output = log.mock.calls.map((call) => call.join(" ")).join("\\n");
      expect(output).toContain("DNS setup");
      expect(output).toContain("openclaw.internal");
    } finally {
      log.mockRestore();
    }
  });
});

describe("parseByteSize", () => {
  it.each([
    ["parses 10kb", "10kb", 10 * 1024],
    ["parses 1mb", "1mb", 1024 * 1024],
    ["parses 2gb", "2gb", 2 * 1024 * 1024 * 1024],
    ["parses shorthand 5k", "5k", 5 * 1024],
    ["parses shorthand 1m", "1m", 1024 * 1024],
  ] as const)("%s", (_name, input, expected) => {
    expect(parseByteSize(input)).toBe(expected);
  });

  it("uses default unit when omitted", () => {
    expect(parseByteSize("123")).toBe(123);
  });

  it.each(["", "nope", "-5kb"] as const)("rejects invalid value %j", (input) => {
    expect(() => parseByteSize(input)).toThrow();
  });
});

describe("parseDurationMs", () => {
  it.each([
    ["parses bare ms", "10000", 10_000],
    ["parses seconds suffix", "10s", 10_000],
    ["parses minutes suffix", "1m", 60_000],
    ["parses hours suffix", "2h", 7_200_000],
    ["parses days suffix", "2d", 172_800_000],
    ["supports decimals", "0.5s", 500],
    ["parses composite hours+minutes", "1h30m", 5_400_000],
    ["parses composite with milliseconds", "2m500ms", 120_500],
  ] as const)("%s", (_name, input, expected) => {
    expect(parseDurationMs(input)).toBe(expected);
  });

  it("rejects invalid composite strings", () => {
    expect(() => parseDurationMs("1h30")).toThrow();
    expect(() => parseDurationMs("1h-30m")).toThrow();
  });
});
