import { describe, it, expect } from "vitest";
import {
  mkdtempSync,
  mkdirSync,
  writeFileSync,
  readFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
  assertOptionalPackageNotPresent,
  addToTranspilePackages,
  splicePluginRulesAt,
  addBoundariesEntry,
  emitTemplateTree,
} from "./core-package-utils.js";

describe("assertOptionalPackageNotPresent", () => {
  it("throws if packages/<name>/ exists in cwd", () => {
    const tmp = mkdtempSync(join(tmpdir(), "core-pkg-"));
    mkdirSync(join(tmp, "packages", "core-foo"), { recursive: true });
    expect(() => assertOptionalPackageNotPresent("core-foo", tmp)).toThrow(/already exists/);
  });

  it("returns silently if packages/<name>/ is absent", () => {
    const tmp = mkdtempSync(join(tmpdir(), "core-pkg-"));
    expect(() => assertOptionalPackageNotPresent("core-foo", tmp)).not.toThrow();
  });
});

describe("addToTranspilePackages", () => {
  it("inserts package name alphabetically into transpilePackages array", () => {
    const tmp = mkdtempSync(join(tmpdir(), "core-pkg-"));
    const cfgPath = join(tmp, "next.config.mjs");
    writeFileSync(
      cfgPath,
      `const nextConfig = {
  transpilePackages: [
    "@repo/core-cms",
    "@repo/core-shared",
  ],
};
`,
    );
    addToTranspilePackages(cfgPath, "@repo/core-realtime");
    const result = readFileSync(cfgPath, "utf8");
    // Order should be: core-cms, core-realtime, core-shared
    const order = result.match(/@repo\/core-\w+/g) ?? [];
    expect(order).toEqual(["@repo/core-cms", "@repo/core-realtime", "@repo/core-shared"]);
  });

  it("is idempotent — duplicate insertion is a no-op", () => {
    const tmp = mkdtempSync(join(tmpdir(), "core-pkg-"));
    const cfgPath = join(tmp, "next.config.mjs");
    writeFileSync(
      cfgPath,
      `const nextConfig = {
  transpilePackages: ["@repo/core-realtime"],
};
`,
    );
    addToTranspilePackages(cfgPath, "@repo/core-realtime");
    const result = readFileSync(cfgPath, "utf8");
    expect(result.match(/@repo\/core-realtime/g)?.length).toBe(1);
  });
});

describe("splicePluginRulesAt", () => {
  it("inserts rule block at the named anchor", () => {
    const tmp = mkdtempSync(join(tmpdir(), "core-pkg-"));
    const path = join(tmp, "base.js");
    writeFileSync(
      path,
      `before\n// <gen:realtime-rules>\nafter\n`,
    );
    splicePluginRulesAt(path, "realtime-rules", "INSERTED_BLOCK");
    const result = readFileSync(path, "utf8");
    expect(result).toContain("// <gen:realtime-rules>\nINSERTED_BLOCK\nafter");
  });

  it("is idempotent — re-inserting same block at anchor is a no-op", () => {
    const tmp = mkdtempSync(join(tmpdir(), "core-pkg-"));
    const path = join(tmp, "base.js");
    writeFileSync(
      path,
      `// <gen:realtime-rules>\nINSERTED_BLOCK\nrest\n`,
    );
    splicePluginRulesAt(path, "realtime-rules", "INSERTED_BLOCK");
    const result = readFileSync(path, "utf8");
    expect(result.match(/INSERTED_BLOCK/g)?.length).toBe(1);
  });
});

describe("addBoundariesEntry", () => {
  it("inserts entry before the packages/core-* wildcard", () => {
    const tmp = mkdtempSync(join(tmpdir(), "core-pkg-"));
    const path = join(tmp, "base.js");
    writeFileSync(
      path,
      `"boundaries/elements": [
        { type: "core-composition", pattern: "packages/core-cms" },
        { type: "core", pattern: "packages/core-*" },
        { type: "feature", pattern: "packages/!(core-*)" },
      ],`,
    );
    addBoundariesEntry(path, "packages/core-realtime", { mode: "folder" });
    const result = readFileSync(path, "utf8");
    // The new entry must appear BEFORE the packages/core-* wildcard
    const newIdx = result.indexOf(`pattern: "packages/core-realtime"`);
    const wildcardIdx = result.indexOf(`pattern: "packages/core-*"`);
    expect(newIdx).toBeGreaterThan(0);
    expect(newIdx).toBeLessThan(wildcardIdx);
  });

  it("is idempotent — re-inserting same entry is a no-op", () => {
    const tmp = mkdtempSync(join(tmpdir(), "core-pkg-"));
    const path = join(tmp, "base.js");
    writeFileSync(
      path,
      `"boundaries/elements": [
        { type: "core", pattern: "packages/core-realtime", mode: "folder" },
        { type: "core", pattern: "packages/core-*" },
      ],`,
    );
    addBoundariesEntry(path, "packages/core-realtime", { mode: "folder" });
    const result = readFileSync(path, "utf8");
    expect(result.match(/packages\/core-realtime/g)?.length).toBe(1);
  });
});

describe("emitTemplateTree", () => {
  it("produces an `add` plop action per .hbs file in the template directory", () => {
    // emitTemplateTree reads from turbo/generators/templates/ — the test uses a
    // temp template directory injected via the `templatesRoot` arg.
    const tmpTemplates = mkdtempSync(join(tmpdir(), "tpl-"));
    mkdirSync(join(tmpTemplates, "core-package", "demo", "src"), { recursive: true });
    writeFileSync(join(tmpTemplates, "core-package", "demo", "package.json.hbs"), "{}");
    writeFileSync(join(tmpTemplates, "core-package", "demo", "src", "index.ts.hbs"), "export {};");
    const actions = emitTemplateTree("core-package/demo", "packages/demo", { templatesRoot: tmpTemplates });
    expect(actions).toHaveLength(2);
    expect(actions[0]!.type).toBe("add");
    const paths = actions.map((a) => (a as { path: string }).path);
    expect(paths).toContain("packages/demo/package.json");
    expect(paths).toContain("packages/demo/src/index.ts");
  });
});
