import { beforeEach, describe, expect, it, vi } from "vitest";
import type { IAiLogger } from "@gadget/ai";
import { AiToolbox, type DroneToolboxEnvironment } from "../toolbox.ts";
import { SubprocessTool } from "./subprocess.ts";

vi.mock("../../services/subprocess.ts", () => ({
  default: {
    create: vi.fn(),
    ps: vi.fn(),
    killPid: vi.fn(),
    killAll: vi.fn(),
    haltPid: vi.fn(),
    getLog: vi.fn(),
  },
}));

const mockLogger: IAiLogger = {
  debug: vi.fn(),
  info: vi.fn(),
  warn: vi.fn(),
  error: vi.fn(),
};

const env: DroneToolboxEnvironment = {
  NODE_ENV: "test",
  workspace: {
    workspaceDir: "/tmp/workspace",
    projectDir: "/tmp/workspace/test-project",
    cacheDir: "/tmp/workspace/.gadget/cache",
  },
};

describe("SubprocessTool", () => {
  let toolbox: AiToolbox;
  let tool: SubprocessTool;

  beforeEach(() => {
    toolbox = new AiToolbox(env);
    tool = new SubprocessTool(toolbox);
    vi.clearAllMocks();
  });

  it("has the correct name and category", () => {
    expect(tool.name).toBe("subprocess");
    expect(tool.category).toBe("system");
  });

  it("defines the subprocess tool definition with all commands", () => {
    const params = tool.definition.function.parameters as Record<string, any>;
    const cmd = params.properties?.cmd as Record<string, any> | undefined;
    const cmds: string[] = cmd?.enum ?? [];

    expect(tool.definition.type).toBe("function");
    expect(tool.definition.function.name).toBe("subprocess");
    expect(cmds).toContain("create");
    expect(cmds).toContain("list");
    expect(cmds).toContain("kill");
    expect(cmds).toContain("killAll");
    expect(cmds).toContain("halt");
    expect(cmds).toContain("log");
  });

  it("returns error when cmd is missing", async () => {
    const result = await tool.execute({}, mockLogger);
    expect(result).toContain("MISSING_PARAMETER");
    expect(result).toContain("cmd");
  });

  it("returns error when cmd is invalid", async () => {
    const result = await tool.execute({ cmd: "invalid" }, mockLogger);
    expect(result).toContain("INVALID_PARAMETER");
  });

  describe("cmd=create", () => {
    it("returns error when command is missing", async () => {
      const result = await tool.execute({ cmd: "create" }, mockLogger);
      expect(result).toContain("MISSING_PARAMETER");
      expect(result).toContain("command");
    });

    it("calls SubProcessService.create with args", async () => {
      const SubProcessService = (await import("../../services/subprocess.ts")).default;
      (SubProcessService.create as ReturnType<typeof vi.fn>).mockResolvedValue({
        pid: 42,
        stdoutFileName: "/tmp/.gadget/subprocess-logs/subproc-42.stdout.log",
        stderrFileName: "/tmp/.gadget/subprocess-logs/subproc-42.stderr.log",
      });

      const result = await tool.execute(
        { cmd: "create", command: "node", args: ["server.js"] },
        mockLogger,
      );

      expect(SubProcessService.create).toHaveBeenCalledWith(
        expect.objectContaining({ slug: "test-project" }),
        "node",
        ["server.js"],
      );

      expect(result).toContain("SUBPROCESS CREATED");
      expect(result).toContain("pid: 42");
    });

    it("handles args as non-array gracefully", async () => {
      const SubProcessService = (await import("../../services/subprocess.ts")).default;
      (SubProcessService.create as ReturnType<typeof vi.fn>).mockResolvedValue({
        pid: 7,
        stdoutFileName: "/tmp/subproc-7.stdout.log",
        stderrFileName: "/tmp/subproc-7.stderr.log",
      });

      const result = await tool.execute(
        { cmd: "create", command: "echo", args: "hello" },
        mockLogger,
      );

      expect(result).toContain("pid: 7");
    });
  });

  describe("cmd=list", () => {
    it("returns empty list message when no processes", async () => {
      const SubProcessService = (await import("../../services/subprocess.ts")).default;
      (SubProcessService.ps as ReturnType<typeof vi.fn>).mockReturnValue([]);

      const result = await tool.execute({ cmd: "list" }, mockLogger);
      expect(result).toContain("(no running subprocesses)");
    });

    it("lists running processes", async () => {
      const SubProcessService = (await import("../../services/subprocess.ts")).default;
      (SubProcessService.ps as ReturnType<typeof vi.fn>).mockReturnValue([
        {
          pid: 1,
          project: { slug: "project-a" },
          createdAt: new Date("2026-01-01"),
          updatedAt: new Date("2026-01-02"),
        },
        {
          pid: 2,
          project: { slug: "project-b" },
          createdAt: new Date("2026-01-03"),
          updatedAt: new Date("2026-01-04"),
        },
      ]);

      const result = await tool.execute({ cmd: "list" }, mockLogger);
      expect(result).toContain("SUBPROCESS LIST");
      expect(result).toContain("pid: 1");
      expect(result).toContain("pid: 2");
      expect(result).toContain("project-a");
      expect(result).toContain("project-b");
    });
  });

  describe("cmd=kill", () => {
    it("returns error when pid is missing", async () => {
      const result = await tool.execute({ cmd: "kill" }, mockLogger);
      expect(result).toContain("MISSING_PARAMETER");
      expect(result).toContain("pid");
    });

    it("calls killPid and returns success", async () => {
      const SubProcessService = (await import("../../services/subprocess.ts")).default;
      (SubProcessService.killPid as ReturnType<typeof vi.fn>).mockResolvedValue(true);

      const result = await tool.execute({ cmd: "kill", pid: 42 }, mockLogger);
      expect(SubProcessService.killPid).toHaveBeenCalledWith(42);
      expect(result).toContain("SUBPROCESS KILLED");
      expect(result).toContain("pid: 42");
    });

    it("handles service errors", async () => {
      const SubProcessService = (await import("../../services/subprocess.ts")).default;
      (SubProcessService.killPid as ReturnType<typeof vi.fn>).mockRejectedValue(new Error("not found"));

      const result = await tool.execute({ cmd: "kill", pid: 99 }, mockLogger);
      expect(result).toContain("OPERATION_FAILED");
    });
  });

  describe("cmd=killAll", () => {
    it("calls killAll and returns success", async () => {
      const SubProcessService = (await import("../../services/subprocess.ts")).default;
      (SubProcessService.killAll as ReturnType<typeof vi.fn>).mockResolvedValue(true);

      const result = await tool.execute({ cmd: "killAll" }, mockLogger);
      expect(SubProcessService.killAll).toHaveBeenCalledOnce();
      expect(result).toContain("SUBPROCESS KILLALL");
    });
  });

  describe("cmd=halt", () => {
    it("returns error when pid is missing", async () => {
      const result = await tool.execute({ cmd: "halt" }, mockLogger);
      expect(result).toContain("MISSING_PARAMETER");
      expect(result).toContain("pid");
    });

    it("calls haltPid and returns success", async () => {
      const SubProcessService = (await import("../../services/subprocess.ts")).default;
      (SubProcessService.haltPid as ReturnType<typeof vi.fn>).mockResolvedValue(true);

      const result = await tool.execute({ cmd: "halt", pid: 42 }, mockLogger);
      expect(SubProcessService.haltPid).toHaveBeenCalledWith(42);
      expect(result).toContain("SUBPROCESS HALTED");
      expect(result).toContain("pid: 42");
    });
  });

  describe("cmd=log", () => {
    it("returns error when pid is missing", async () => {
      const result = await tool.execute({ cmd: "log", which: "stdout" }, mockLogger);
      expect(result).toContain("MISSING_PARAMETER");
      expect(result).toContain("pid");
    });

    it("returns error when which is missing", async () => {
      const result = await tool.execute({ cmd: "log", pid: 42 }, mockLogger);
      expect(result).toContain("MISSING_PARAMETER");
      expect(result).toContain("which");
    });

    it("returns error when which is invalid", async () => {
      const result = await tool.execute({ cmd: "log", pid: 42, which: "invalid" }, mockLogger);
      expect(result).toContain("MISSING_PARAMETER");
      expect(result).toContain("which");
    });

    it("reads stdout log content", async () => {
      const SubProcessService = (await import("../../services/subprocess.ts")).default;
      (SubProcessService.getLog as ReturnType<typeof vi.fn>).mockResolvedValue("line1\nline2\nline3");

      const result = await tool.execute(
        { cmd: "log", pid: 42, which: "stdout" },
        mockLogger,
      );

      expect(SubProcessService.getLog).toHaveBeenCalledWith(42, "stdout");
      expect(result).toContain("SUBPROCESS LOG (stdout)");
      expect(result).toContain("pid: 42");
      expect(result).toContain("line1");
      expect(result).toContain("line2");
      expect(result).toContain("line3");
    });

    it("reads stderr log content", async () => {
      const SubProcessService = (await import("../../services/subprocess.ts")).default;
      (SubProcessService.getLog as ReturnType<typeof vi.fn>).mockResolvedValue("error: something broke");

      const result = await tool.execute(
        { cmd: "log", pid: 42, which: "stderr" },
        mockLogger,
      );

      expect(SubProcessService.getLog).toHaveBeenCalledWith(42, "stderr");
      expect(result).toContain("SUBPROCESS LOG (stderr)");
      expect(result).toContain("error: something broke");
    });
  });
});
