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

// We reset the singleton between tests by calling resetSkillRegistryDb
// and redirecting PAPERCLIP_HOME to an isolated temp dir

describe("skill-registry-schema", () => {
  let tmpDir: string;
  let originalPaperclipHome: string | undefined;

  beforeEach(async () => {
    tmpDir = await mkdtemp(path.join(os.tmpdir(), "skill-registry-test-"));
    originalPaperclipHome = process.env.PAPERCLIP_HOME;
    process.env.PAPERCLIP_HOME = tmpDir;
  });

  afterEach(async () => {
    // Reset the DB singleton so next test gets a fresh instance
    const { resetSkillRegistryDb } = await import("../services/skill-registry-db.js");
    resetSkillRegistryDb();

    if (originalPaperclipHome === undefined) {
      delete process.env.PAPERCLIP_HOME;
    } else {
      process.env.PAPERCLIP_HOME = originalPaperclipHome;
    }
    await rm(tmpDir, { recursive: true, force: true });
  });

  it("Test 1: getSkillRegistryDb() creates registry.db at the resolved path and returns a drizzle instance", async () => {
    const { getSkillRegistryDb } = await import("../services/skill-registry-db.js");
    const db = await getSkillRegistryDb();
    expect(db).toBeDefined();
    expect(typeof db.select).toBe("function");
    expect(typeof db.insert).toBe("function");
  });

  it("Test 2: skills table has correct columns", async () => {
    const { skills } = await import("../services/skill-registry-schema.js");
    const cols = Object.keys(skills);
    // Check the table has the expected name
    expect((skills as any)[Symbol.for("drizzle:Name") as any] ?? skills._.name).toBeDefined();
    const colNames = Object.keys(skills);
    // Drizzle table object has column accessors
    expect(skills.id).toBeDefined();
    expect(skills.sourceId).toBeDefined();
    expect(skills.name).toBeDefined();
    expect(skills.description).toBeDefined();
    expect(skills.sourceUrl).toBeDefined();
    expect(skills.activeVersionId).toBeDefined();
    expect(skills.removedAt).toBeDefined();
    expect(skills.createdAt).toBeDefined();
    expect(skills.updatedAt).toBeDefined();
  });

  it("Test 3: skill_versions table has correct columns", async () => {
    const { skillVersions } = await import("../services/skill-registry-schema.js");
    expect(skillVersions.id).toBeDefined();
    expect(skillVersions.skillId).toBeDefined();
    expect(skillVersions.version).toBeDefined();
    expect(skillVersions.fetchedAt).toBeDefined();
    expect(skillVersions.cacheDir).toBeDefined();
  });

  it("Test 4: skill_files table has correct columns", async () => {
    const { skillFiles } = await import("../services/skill-registry-schema.js");
    expect(skillFiles.id).toBeDefined();
    expect(skillFiles.versionId).toBeDefined();
    expect(skillFiles.path).toBeDefined();
    expect(skillFiles.kind).toBeDefined();
    expect(skillFiles.sizeBytes).toBeDefined();
  });

  it("Test 5: community_ratings table has correct columns", async () => {
    const { communityRatings } = await import("../services/skill-registry-schema.js");
    expect(communityRatings.id).toBeDefined();
    expect(communityRatings.skillId).toBeDefined();
    expect(communityRatings.fetchedAt).toBeDefined();
    expect(communityRatings.averageRating).toBeDefined();
    expect(communityRatings.ratingCount).toBeDefined();
    expect(communityRatings.source).toBeDefined();
  });

  it("Test 6: soft-delete — inserting a skill and setting removed_at keeps the row queryable with a WHERE filter", async () => {
    const { getSkillRegistryDb } = await import("../services/skill-registry-db.js");
    const { skills } = await import("../services/skill-registry-schema.js");
    const { eq, isNull, isNotNull } = await import("drizzle-orm");

    const db = await getSkillRegistryDb();
    const now = Date.now();

    await db.insert(skills).values({
      id: "test-skill-1",
      sourceId: "src-1",
      name: "Test Skill",
      description: "A test skill",
      sourceUrl: null,
      activeVersionId: null,
      removedAt: null,
      createdAt: now,
      updatedAt: now,
    });

    // Before soft-delete: row is visible
    const before = await db.select().from(skills).where(isNull(skills.removedAt));
    expect(before.length).toBe(1);

    // Apply soft-delete
    await db.update(skills).set({ removedAt: now + 1000 }).where(eq(skills.id, "test-skill-1"));

    // After soft-delete: not visible via active filter
    const activeAfter = await db.select().from(skills).where(isNull(skills.removedAt));
    expect(activeAfter.length).toBe(0);

    // But still visible via removed filter
    const removedAfter = await db.select().from(skills).where(isNotNull(skills.removedAt));
    expect(removedAfter.length).toBe(1);
    expect(removedAfter[0]!.id).toBe("test-skill-1");
  });

  it("Test 7: resolveSkillRegistryDbPath() returns path ending in skills/registry.db under instance root", async () => {
    const { resolveSkillRegistryDbPath } = await import("../home-paths.js");
    const dbPath = resolveSkillRegistryDbPath();
    expect(dbPath).toMatch(/skills[/\\]registry\.db$/);
    expect(dbPath.startsWith(tmpDir)).toBe(true);
  });

  it("Test 8: resolveSkillCacheDir returns path ending in skills/cache/<skillId>/<versionId>", async () => {
    const { resolveSkillCacheDir } = await import("../home-paths.js");
    const cacheDir = resolveSkillCacheDir("my-skill", "abc123");
    expect(cacheDir).toMatch(/skills[/\\]cache[/\\]my-skill[/\\]abc123$/);
    expect(cacheDir.startsWith(tmpDir)).toBe(true);
  });
});
