import { beforeEach, describe, expect, it, vi } from "vitest";
import { IProject } from "@gadget/api";
import SubProcessService, { type SubProcess } from "./subprocess.ts";

vi.mock("./workspace.js", () => ({
  default: {
    gadgetDir: "/tmp/.gadget",
    getProjectDirectory: vi.fn((slug: string) => process.cwd()),
  },
}));

function makeProject(): IProject {
  return {
    _id: "proj-test",
    slug: "test-project",
    name: "Test Project",
    createdAt: new Date(),
    updatedAt: new Date(),
    createdBy: "system",
  } as unknown as IProject;
}

const NODE = process.execPath;

describe("SubProcessService", () => {
  beforeEach(async () => {
    await SubProcessService.start();
  });

  it("starts and stops cleanly", async () => {
    await SubProcessService.stop();
    expect(true).toBe(true);
  });

  it("creates a subprocess and tracks it in ps", async () => {
    const project = makeProject();
    const sp = await SubProcessService.create(project, NODE, ["-e", "process.exit(0)"]);

    expect(sp.pid).toBeGreaterThan(0);
    expect(sp.project.slug).toBe("test-project");
    expect(sp.stdoutFileName).toContain("subproc-");
    expect(sp.stdoutFileName).toContain(".stdout.log");
    expect(sp.stderrFileName).toContain(".stderr.log");

    const list = SubProcessService.ps();
    expect(list.some((p) => p.pid === sp.pid)).toBe(true);

    await SubProcessService.killPid(sp.pid);
  });

  it("create throws if workspace is not initialized", async () => {
    const wsModule = await import("./workspace.js");
    const ws = wsModule.default as { gadgetDir: string | undefined };
    const original = ws.gadgetDir;
    ws.gadgetDir = undefined;

    const project = makeProject();
    await expect(
      SubProcessService.create(project, NODE, ["-e", "process.exit(0)"]),
    ).rejects.toThrow("Gadget workspace directory not initialized");

    ws.gadgetDir = original;
  });

  it("ps returns empty array when no processes exist", () => {
    const list = SubProcessService.ps();
    expect(Array.isArray(list)).toBe(true);
    expect(list.length).toBe(0);
  });

  it("killPid terminates a running subprocess", async () => {
    const project = makeProject();
    const sp = await SubProcessService.create(project, NODE, ["-e", "setTimeout(() => {}, 60000)"]);

    expect(SubProcessService.ps().length).toBe(1);

    const killed = await SubProcessService.killPid(sp.pid);
    expect(killed).toBe(true);

    expect(SubProcessService.ps().length).toBe(0);
  });

  it("killPid throws for unknown pid", async () => {
    await expect(SubProcessService.killPid(99999)).rejects.toThrow("process not found");
  });

  it("killAll terminates all subprocesses", async () => {
    const project = makeProject();
    await SubProcessService.create(project, NODE, ["-e", "setTimeout(() => {}, 60000)"]);
    await SubProcessService.create(project, NODE, ["-e", "setTimeout(() => {}, 60000)"]);

    expect(SubProcessService.ps().length).toBe(2);

    await SubProcessService.killAll();

    expect(SubProcessService.ps().length).toBe(0);
  });

  it("haltPid stops a process but preserves log files", async () => {
    const project = makeProject();
    const sp = await SubProcessService.create(project, NODE, [
      "-e",
      "setInterval(() => console.log('tick'), 1000)",
    ]);

    expect(SubProcessService.ps().length).toBe(1);

    const halted = await SubProcessService.haltPid(sp.pid);
    expect(halted).toBe(true);

    expect(SubProcessService.ps().length).toBe(0);
  });

  it("haltPid throws for unknown pid", async () => {
    await expect(SubProcessService.haltPid(99999)).rejects.toThrow("process not found");
  });

  it("getLog returns stdout content", async () => {
    const project = makeProject();
    const sp = await SubProcessService.create(project, NODE, [
      "-e",
      "console.log('hello from subprocess'); process.exit(0)",
    ]);

    await new Promise<void>((resolve) => sp.process.on("exit", () => resolve()));

    const stdout = await SubProcessService.getLog(sp.pid, "stdout");
    expect(stdout).toContain("hello from subprocess");
  });

  it("summarize returns serializable summary without ChildProcess/WriteStream", () => {
    const pid = 12345;
    const project = makeProject();
    const now = new Date();
    const sp: SubProcess = {
      project,
      createdAt: now,
      updatedAt: now,
      process: null as any,
      pid,
      stdoutFileName: "/tmp/.gadget/subprocess-logs/subproc-12345.stdout.log",
      stderrFileName: "/tmp/.gadget/subprocess-logs/subproc-12345.stderr.log",
    };

    const summary = SubProcessService.summarize(sp);
    expect(summary.pid).toBe(pid);
    expect(summary.project.slug).toBe("test-project");
    expect(summary.stdoutFileName).toBe(sp.stdoutFileName);
    expect(summary.stderrFileName).toBe(sp.stderrFileName);
    expect(summary.createdAt).toBe(now);
    expect(summary.updatedAt).toBe(now);
  });
});
