import { afterEach, describe, expect, test } from "bun:test"
import path from "path"
import fs from "fs/promises"
import { Effect, Layer } from "effect"
import { Instance } from "../../src/project/instance"
import { CrossSpawnSpawner } from "@saeeol/core/cross-spawn-spawner"
import { ToolRegistry } from "@/tool/registry"
import { disposeAllInstances, provideTmpdirInstance, tmpdir } from "../fixture/fixture" 
import { testEffect } from "../lib/effect"

const node = CrossSpawnSpawner.defaultLayer

const it = testEffect(Layer.mergeAll(ToolRegistry.defaultLayer, node))

afterEach(async () => {
  await disposeAllInstances()
})

describe("tool.registry", () => {
  it.live("plan_exit is always registered regardless of client", () =>
    Effect.gen(function* () {
      const original = process.env["SAEEOL_CLIENT"]
      try {
        for (const client of ["cli", "vscode", "desktop", "app"]) {
          process.env["SAEEOL_CLIENT"] = client
          yield* provideTmpdirInstance(
            () =>
              Effect.gen(function* () {
                const registry = yield* ToolRegistry.Service
                const ids = yield* registry.ids()
                expect(ids).toContain("plan_exit")
              }),
            { git: true },
          )
        }
      } finally {
        if (original === undefined) delete process.env["SAEEOL_CLIENT"]
        else process.env["SAEEOL_CLIENT"] = original
      }
    }),
  )
  test("suggest is registered for cli and vscode only", async () => {
    const original = process.env["SAEEOL_CLIENT"]
    const originalQuestion = process.env["SAEEOL_ENABLE_QUESTION_TOOL"]
    const originalConfig = process.env["SAEEOL_CONFIG_DIR"]
    try {
      for (const client of ["cli", "vscode", "desktop", "app"]) {
        process.env["SAEEOL_CLIENT"] = client
        process.env["SAEEOL_ENABLE_QUESTION_TOOL"] = client === "vscode" ? "true" : "false"
        await using tmp = await tmpdir({ git: true })
        process.env["SAEEOL_CONFIG_DIR"] = tmp.path
        await Instance.provide({
          directory: tmp.path,
          fn: async () => {
            const ids = await ToolRegistry.ids()
            if (client === "cli" || client === "vscode") expect(ids).toContain("suggest")
            else expect(ids).not.toContain("suggest")
          },
        })
      }
    } finally {
      if (original === undefined) delete process.env["SAEEOL_CLIENT"]
      else process.env["SAEEOL_CLIENT"] = original
      if (originalQuestion === undefined) delete process.env["SAEEOL_ENABLE_QUESTION_TOOL"]
      else process.env["SAEEOL_ENABLE_QUESTION_TOOL"] = originalQuestion
      if (originalConfig === undefined) delete process.env["SAEEOL_CONFIG_DIR"]
      else process.env["SAEEOL_CONFIG_DIR"] = originalConfig
    }
  })

  it.live("loads tools from .saeeol/tool (singular)", () =>
    provideTmpdirInstance((dir) =>
      Effect.gen(function* () {
        const saeeol = path.join(dir, ".saeeol")
        const tool = path.join(saeeol, "tool")
        yield* Effect.promise(() => fs.mkdir(tool, { recursive: true }))
        yield* Effect.promise(() =>
          Bun.write(
            path.join(tool, "hello.ts"),
            [
              "export default {",
              "  description: 'hello tool',",
              "  args: {},",
              "  execute: async () => {",
              "    return 'hello world'",
              "  },",
              "}",
              "",
            ].join("\n"),
          ),
        )
        const registry = yield* ToolRegistry.Service
        const ids = yield* registry.ids()
        expect(ids).toContain("hello")
      }),
    ),
  )

  it.live("loads tools from .saeeol/tools (plural)", () =>
    provideTmpdirInstance((dir) =>
      Effect.gen(function* () {
        const saeeol = path.join(dir, ".saeeol")
        const tools = path.join(saeeol, "tools")
        yield* Effect.promise(() => fs.mkdir(tools, { recursive: true }))
        yield* Effect.promise(() =>
          Bun.write(
            path.join(tools, "hello.ts"),
            [
              "export default {",
              "  description: 'hello tool',",
              "  args: {},",
              "  execute: async () => {",
              "    return 'hello world'",
              "  },",
              "}",
              "",
            ].join("\n"),
          ),
        )
        const registry = yield* ToolRegistry.Service
        const ids = yield* registry.ids()
        expect(ids).toContain("hello")
      }),
    ),
  )

  it.live("loads tools with external dependencies without crashing", () =>
    provideTmpdirInstance((dir) =>
      Effect.gen(function* () {
        const saeeol = path.join(dir, ".saeeol")
        const tools = path.join(saeeol, "tools")
        yield* Effect.promise(() => fs.mkdir(tools, { recursive: true }))
        yield* Effect.promise(() =>
          Bun.write(
            path.join(saeeol, "package.json"),
            JSON.stringify({
              name: "custom-tools",
              dependencies: {
                "@saeeol/plugin": "^0.0.0",
                cowsay: "^1.6.0",
              },
            }),
          ),
        )
        yield* Effect.promise(() =>
          Bun.write(
            path.join(saeeol, "package-lock.json"),
            JSON.stringify({
              name: "custom-tools",
              lockfileVersion: 3,
              packages: {
                "": {
                  dependencies: {
                    "@saeeol/plugin": "^0.0.0",
                    cowsay: "^1.6.0",
                  },
                },
              },
            }),
          ),
        )

        const cowsay = path.join(saeeol, "node_modules", "cowsay")
        yield* Effect.promise(() => fs.mkdir(cowsay, { recursive: true }))
        yield* Effect.promise(() =>
          Bun.write(
            path.join(cowsay, "package.json"),
            JSON.stringify({
              name: "cowsay",
              type: "module",
              exports: "./index.js",
            }),
          ),
        )
        yield* Effect.promise(() =>
          Bun.write(
            path.join(cowsay, "index.js"),
            ["export function say({ text }) {", "  return `moo ${text}`", "}", ""].join("\n"),
          ),
        )
        yield* Effect.promise(() =>
          Bun.write(
            path.join(tools, "cowsay.ts"),
            [
              "import { say } from 'cowsay'",
              "export default {",
              "  description: 'tool that imports cowsay at top level',",
              "  args: { text: { type: 'string' } },",
              "  execute: async ({ text }: { text: string }) => {",
              "    return say({ text })",
              "  },",
              "}",
              "",
            ].join("\n"),
          ),
        )
        const registry = yield* ToolRegistry.Service
        const ids = yield* registry.ids()
        expect(ids).toContain("cowsay")
      }),
    ),
  )
})
