import { mkdtemp, rm, mkdir, writeFile, readFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, it, expect, beforeEach, afterEach } from "vitest";

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function mockSkillMd(name: string, description: string, jobType: string): string {
  return `---
name: ${name}
description: ${description}
---

# ${name}

## Usage

POST /api/content-jobs with jobType: "${jobType}"
`;
}

// ---------------------------------------------------------------------------
// Tests — SKILL.md files on disk
// ---------------------------------------------------------------------------

describe("content skill files", () => {
  const SKILL_TYPES = [
    "diagram",
    "icon-set",
    "theme-palette",
    "wallpaper",
    "social-post",
    "convert",
    "pdf-document",
    "brand-kit",
    "presentation",
  ];

  const contentDir = path.resolve(
    new URL("../skills/content", import.meta.url).pathname,
  );

  it("Test 1: 9 .SKILL.md files exist in server/src/skills/content/", async () => {
    const { readdir } = await import("node:fs/promises");
    const entries = await readdir(contentDir);
    const skillFiles = entries.filter((e) => e.endsWith(".SKILL.md"));
    expect(skillFiles).toHaveLength(9);
  });

  it("Test 2: Each SKILL.md has parseable frontmatter with name and description", async () => {
    const { parseSkillFrontmatter } = await import("../services/skill-registry-fetcher.js");

    for (const skillType of SKILL_TYPES) {
      const filePath = path.join(contentDir, `${skillType}.SKILL.md`);
      const content = await readFile(filePath, "utf-8");
      const { name, description } = parseSkillFrontmatter(content);
      expect(name, `${skillType}.SKILL.md missing name`).toBeTruthy();
      expect(description, `${skillType}.SKILL.md missing description`).toBeTruthy();
    }
  });

  it("Test 3: Each SKILL.md contains its jobType string", async () => {
    for (const skillType of SKILL_TYPES) {
      const filePath = path.join(contentDir, `${skillType}.SKILL.md`);
      const content = await readFile(filePath, "utf-8");
      expect(content, `${skillType}.SKILL.md missing jobType reference`).toContain(`jobType`);
    }
  });
});

// ---------------------------------------------------------------------------
// Tests — fetchLocalNexusContent via fetchAllSources
// ---------------------------------------------------------------------------

describe("fetchLocalNexusContent", () => {
  let tmpDir: string;
  let tmpContentDir: string;

  beforeEach(async () => {
    tmpDir = await mkdtemp(path.join(os.tmpdir(), "nexus-content-skills-test-"));
    tmpContentDir = path.join(tmpDir, "skills", "content");
    await mkdir(tmpContentDir, { recursive: true });
    process.env.PAPERCLIP_HOME = tmpDir;

    const { resetSkillRegistryDb } = await import("../services/skill-registry-db.js");
    resetSkillRegistryDb();
  });

  afterEach(async () => {
    const { resetSkillRegistryDb } = await import("../services/skill-registry-db.js");
    resetSkillRegistryDb();
    delete process.env.PAPERCLIP_HOME;
    await rm(tmpDir, { recursive: true, force: true });
  });

  it("Test 4: fetchAllSources with local-nexus-content returns fetched=9 for 9 mock SKILL.md files", async () => {
    // Create 9 mock SKILL.md files
    const types = ["diagram", "icon-set", "theme-palette", "wallpaper", "social-post", "convert", "pdf-document", "brand-kit", "presentation"];
    for (const t of types) {
      await writeFile(
        path.join(tmpContentDir, `${t}.SKILL.md`),
        mockSkillMd(`${t} Skill`, `Does ${t} stuff`, t),
        "utf-8",
      );
    }

    const { fetchAllSources } = await import("../services/skill-registry-fetcher.js");
    const localSource = {
      id: "nexus-content",
      type: "local-nexus-content" as const,
      dir: tmpContentDir,
      label: "Nexus Content Tools",
    };

    const result = await fetchAllSources([localSource]);
    expect(result.errors).toHaveLength(0);
    expect(result.fetched).toBe(9);
  });

  it("Test 5: Skills are upserted into the DB with correct sourceId and skillId format", async () => {
    await writeFile(
      path.join(tmpContentDir, "diagram.SKILL.md"),
      mockSkillMd("Diagram Generator", "Generates diagrams", "diagram"),
      "utf-8",
    );

    const { fetchAllSources } = await import("../services/skill-registry-fetcher.js");
    const { getSkillRegistryDb } = await import("../services/skill-registry-db.js");
    const { skills } = await import("../services/skill-registry-schema.js");

    const localSource = {
      id: "nexus-content",
      type: "local-nexus-content" as const,
      dir: tmpContentDir,
      label: "Nexus Content Tools",
    };

    await fetchAllSources([localSource]);
    const db = await getSkillRegistryDb();
    const rows = await db.select().from(skills);

    expect(rows).toHaveLength(1);
    expect(rows[0]!.id).toBe("nexus-content/diagram");
    expect(rows[0]!.sourceId).toBe("nexus-content");
    expect(rows[0]!.name).toBe("Diagram Generator");
  });

  it("Test 6: Duplicate fetch (restart) does not create duplicate rows — idempotent", async () => {
    await writeFile(
      path.join(tmpContentDir, "diagram.SKILL.md"),
      mockSkillMd("Diagram Generator", "Generates diagrams", "diagram"),
      "utf-8",
    );

    const { fetchAllSources } = await import("../services/skill-registry-fetcher.js");
    const { getSkillRegistryDb } = await import("../services/skill-registry-db.js");
    const { skills, skillVersions } = await import("../services/skill-registry-schema.js");

    const localSource = {
      id: "nexus-content",
      type: "local-nexus-content" as const,
      dir: tmpContentDir,
      label: "Nexus Content Tools",
    };

    await fetchAllSources([localSource]);
    await fetchAllSources([localSource]);

    const db = await getSkillRegistryDb();
    const skillRows = await db.select().from(skills);
    const versionRows = await db.select().from(skillVersions);

    // Should be exactly 1 skill and 1 version (no duplicates)
    expect(skillRows).toHaveLength(1);
    expect(versionRows).toHaveLength(1);
  });

  it("Test 7: Missing directory returns fetched=0 with no errors", async () => {
    const { fetchAllSources } = await import("../services/skill-registry-fetcher.js");
    const localSource = {
      id: "nexus-content",
      type: "local-nexus-content" as const,
      dir: "/nonexistent/path/that/does/not/exist",
      label: "Nexus Content Tools",
    };

    const result = await fetchAllSources([localSource]);
    expect(result.fetched).toBe(0);
    expect(result.errors).toHaveLength(0);
  });
});

// ---------------------------------------------------------------------------
// Tests — Creative group members (placeholder — filled in by Task 2)
// ---------------------------------------------------------------------------

describe("Creative group members", () => {
  let tmpDir: string;

  beforeEach(async () => {
    tmpDir = await mkdtemp(path.join(os.tmpdir(), "nexus-creative-group-test-"));
    process.env.PAPERCLIP_HOME = tmpDir;

    const { resetSkillRegistryDb } = await import("../services/skill-registry-db.js");
    resetSkillRegistryDb();
  });

  afterEach(async () => {
    const { resetSkillRegistryDb } = await import("../services/skill-registry-db.js");
    resetSkillRegistryDb();
    delete process.env.PAPERCLIP_HOME;
    await rm(tmpDir, { recursive: true, force: true });
  });

  it("Test 8: seedCreativeGroupMembers inserts 9 rows for builtin/creative group", async () => {
    const { getSkillRegistryDb, seedCreativeGroupMembers } = await import("../services/skill-registry-db.js");

    // Initialize DB (creates tables and builtin groups)
    await getSkillRegistryDb();

    // Seed the group members
    await seedCreativeGroupMembers();

    // Query directly
    const { getRawClient } = await import("../services/skill-registry-db.js");
    const client = getRawClient();
    const result = await client.execute({
      sql: `SELECT skill_id FROM skill_group_members WHERE group_id = ?`,
      args: ["builtin/creative"],
    });

    expect(result.rows).toHaveLength(9);
    const skillIds = result.rows.map((r) => r[0] as string);
    expect(skillIds).toContain("nexus-content/diagram");
    expect(skillIds).toContain("nexus-content/presentation");
  });

  it("Test 9: seedCreativeGroupMembers is idempotent — calling twice does not duplicate rows", async () => {
    const { getSkillRegistryDb, seedCreativeGroupMembers } = await import("../services/skill-registry-db.js");

    await getSkillRegistryDb();
    await seedCreativeGroupMembers();
    await seedCreativeGroupMembers();

    const { getRawClient } = await import("../services/skill-registry-db.js");
    const client = getRawClient();
    const result = await client.execute({
      sql: `SELECT skill_id FROM skill_group_members WHERE group_id = ?`,
      args: ["builtin/creative"],
    });

    // Still exactly 9, not 18
    expect(result.rows).toHaveLength(9);
  });
});
