import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { mkdtemp, rm } from "node:fs/promises";
import path from "node:path";
import os from "node:os";
import { getSkillRegistryDb, resetSkillRegistryDb } from "../services/skill-registry-db.js";
import { agentSkills } from "../services/skill-registry-schema.js";
import { skillRatingService } from "../services/skill-registry-ratings.js";

// ---------------------------------------------------------------------------
// Test setup
// ---------------------------------------------------------------------------

let tmpHome: string;

beforeEach(async () => {
  tmpHome = await mkdtemp(path.join(os.tmpdir(), "nexus-ratings-test-"));
  process.env.PAPERCLIP_HOME = tmpHome;
  resetSkillRegistryDb();
});

afterEach(async () => {
  resetSkillRegistryDb();
  delete process.env.PAPERCLIP_HOME;
  await rm(tmpHome, { recursive: true, force: true });
});

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("skillRatingService", () => {
  const svc = skillRatingService();

  it("Test 1: rate() inserts a personal_ratings row; getRatings(skillId) returns it with correct fields", async () => {
    await svc.rate({ skillId: "src/skill-a", versionId: "v1", stars: 4, note: "Great skill" });
    const ratings = await svc.getRatings("src/skill-a");
    expect(ratings).toHaveLength(1);
    expect(ratings[0]!.skillId).toBe("src/skill-a");
    expect(ratings[0]!.versionId).toBe("v1");
    expect(ratings[0]!.stars).toBe(4);
    expect(ratings[0]!.note).toBe("Great skill");
  });

  it("Test 2: rate() called twice for same skill creates two separate rows (append-only)", async () => {
    await svc.rate({ skillId: "src/skill-b", stars: 3 });
    await svc.rate({ skillId: "src/skill-b", stars: 5, note: "Even better now" });
    const ratings = await svc.getRatings("src/skill-b");
    expect(ratings).toHaveLength(2);
  });

  it("Test 3: getRatings() returns results ordered by created_at descending (newest first)", async () => {
    await svc.rate({ skillId: "src/skill-c", stars: 2, note: "First" });
    // Small delay to ensure different timestamps
    await new Promise((r) => setTimeout(r, 5));
    await svc.rate({ skillId: "src/skill-c", stars: 5, note: "Second" });

    const ratings = await svc.getRatings("src/skill-c");
    expect(ratings).toHaveLength(2);
    // Newest first — the second rating should be first in results
    expect(ratings[0]!.note).toBe("Second");
    expect(ratings[1]!.note).toBe("First");
  });

  it("Test 4: recordUsageForAgent() increments task_count from 0 to 1 for all agent_skills rows of the agent", async () => {
    const db = await getSkillRegistryDb();
    const now = Date.now();
    // Insert two agent_skills rows for the same agent
    await db.insert(agentSkills).values([
      { agentId: "agent-1", skillId: "src/skill-x", installedAt: now },
      { agentId: "agent-1", skillId: "src/skill-y", installedAt: now },
    ]);

    await svc.recordUsageForAgent("agent-1", null);

    const { eq } = await import("drizzle-orm");
    const rows = await db.select().from(agentSkills).where(eq(agentSkills.agentId, "agent-1"));
    for (const row of rows) {
      expect((row as any).taskCount ?? (row as any).task_count).toBe(1);
    }
  });

  it("Test 5: recordUsageForAgent() computes running average: after two calls with costs 0.01 and 0.03, avg_cost_usd is 0.02", async () => {
    const db = await getSkillRegistryDb();
    const now = Date.now();
    await db.insert(agentSkills).values({ agentId: "agent-2", skillId: "src/skill-z", installedAt: now });

    await svc.recordUsageForAgent("agent-2", 0.01);
    await svc.recordUsageForAgent("agent-2", 0.03);

    const { eq } = await import("drizzle-orm");
    const rows = await db.select().from(agentSkills).where(eq(agentSkills.agentId, "agent-2"));
    const row = rows[0] as any;
    const avgCost = row.avgCostUsd ?? row.avg_cost_usd;
    expect(avgCost).toBeCloseTo(0.02, 5);
  });

  it("Test 6: recordUsageForAgent() sets last_used_at to approximately Date.now()", async () => {
    const db = await getSkillRegistryDb();
    const now = Date.now();
    await db.insert(agentSkills).values({ agentId: "agent-3", skillId: "src/skill-w", installedAt: now });

    const before = Date.now();
    await svc.recordUsageForAgent("agent-3", null);
    const after = Date.now();

    const { eq } = await import("drizzle-orm");
    const rows = await db.select().from(agentSkills).where(eq(agentSkills.agentId, "agent-3"));
    const row = rows[0] as any;
    const lastUsedAt = row.lastUsedAt ?? row.last_used_at;
    expect(lastUsedAt).toBeGreaterThanOrEqual(before);
    expect(lastUsedAt).toBeLessThanOrEqual(after);
  });

  it("Test 7: recordUsageForAgent() does nothing (no throw) when agent has no agent_skills rows", async () => {
    await expect(svc.recordUsageForAgent("nonexistent-agent", 0.05)).resolves.not.toThrow();
  });

  it("Test 8: rate() with stars=0 or stars=6 throws validation error", async () => {
    await expect(svc.rate({ skillId: "src/skill-v", stars: 0 })).rejects.toThrow();
    await expect(svc.rate({ skillId: "src/skill-v", stars: 6 })).rejects.toThrow();
  });
});
