import { describe, it, expect, beforeAll, afterAll } from "vitest";
import prisma from "../config/database";
import {
  listPlanCategories,
  createPlanCategory,
  updatePlanCategory,
  deactivatePlanCategory,
  deletePlanCategory,
  isCategoryKeyActive,
  slugifyCategory,
} from "../services/planCategory.service";

const created: number[] = [];

// Every test category's slug starts with "test_" AND contains "_zz" (labels all
// read "Test … ZZ"). Cleaning by this pattern — not just by in-memory ids —
// means a previous crashed run can't leave a `test_kategorie_zz` row behind that
// makes the dedupe test wrongly assert `_3`/`_4` instead of `_2`. The narrow
// pattern avoids touching real seed categories (work/other/…).
async function cleanupByPrefix() {
  await prisma.plan_categories.deleteMany({
    where: { key: { startsWith: "test_", contains: "_zz" } },
  });
}

beforeAll(async () => {
  await cleanupByPrefix();
});

afterAll(async () => {
  if (created.length) {
    await prisma.plan_categories.deleteMany({ where: { id: { in: created } } });
  }
  // Belt-and-suspenders: also remove anything matching the slug prefix in case
  // a test threw before pushing its id into `created`.
  await cleanupByPrefix();
  await prisma.$disconnect();
});

describe("slugifyCategory", () => {
  it("strips Czech diacritics and non-alphanumerics", () => {
    expect(slugifyCategory("Cesta / Montáž")).toBe("cesta_montaz");
    expect(slugifyCategory("Příprava")).toBe("priprava");
  });

  it("collapses an all-symbol label to an empty slug", () => {
    // No alphanumerics survive → empty string. createPlanCategory's uniqueKey
    // then falls back to "kategorie" so the row is still creatable.
    expect(slugifyCategory("***")).toBe("");
    expect(slugifyCategory("  /  ")).toBe("");
  });
});

describe("plan category service", () => {
  it("seeds are present and ordered", async () => {
    const cats = await listPlanCategories(true);
    const keys = cats.map((c) => c.key);
    expect(keys).toContain("work");
    expect(keys).toContain("other");
  });

  it("creates a category with a unique slug and next sort_order", async () => {
    const res = await createPlanCategory({
      label: "Test Kategorie ZZ",
      color: "#123456",
    });
    if ("error" in res) throw new Error(res.error);
    created.push(res.data.id);
    expect(res.data.key).toBe("test_kategorie_zz");
    expect(res.data.color).toBe("#123456");
    expect(res.data.is_active).toBe(true);
    expect(await isCategoryKeyActive("test_kategorie_zz")).toBe(true);
  });

  it("dedupes a colliding slug", async () => {
    const res = await createPlanCategory({
      label: "Test Kategorie ZZ",
      color: "#222222",
    });
    if ("error" in res) throw new Error(res.error);
    created.push(res.data.id);
    expect(res.data.key).toBe("test_kategorie_zz_2");
  });

  it("updates label and color (key immutable)", async () => {
    const res = await createPlanCategory({
      label: "Test Upd ZZ",
      color: "#aaaaaa",
    });
    if ("error" in res) throw new Error(res.error);
    created.push(res.data.id);
    const upd = await updatePlanCategory(res.data.id, {
      label: "Test Upd2 ZZ",
      color: "#bbbbbb",
    });
    if ("error" in upd) throw new Error(upd.error);
    expect(upd.data.label).toBe("Test Upd2 ZZ");
    expect(upd.data.color).toBe("#bbbbbb");
    expect(upd.data.key).toBe("test_upd_zz");
  });

  it("deactivate hides the key from active check", async () => {
    const res = await createPlanCategory({
      label: "Test Deact ZZ",
      color: "#cccccc",
    });
    if ("error" in res) throw new Error(res.error);
    created.push(res.data.id);
    await deactivatePlanCategory(res.data.id);
    expect(await isCategoryKeyActive("test_deact_zz")).toBe(false);
  });

  it("hard-deletes an unused category", async () => {
    const res = await createPlanCategory({
      label: "Test Del ZZ",
      color: "#dddddd",
    });
    if ("error" in res) throw new Error(res.error);
    const del = await deletePlanCategory(res.data.id);
    expect("data" in del).toBe(true);
    const stillThere = await prisma.plan_categories.findUnique({
      where: { id: res.data.id },
    });
    expect(stillThere).toBeNull();
  });

  it("refuses to hard-delete a category in use", async () => {
    const res = await createPlanCategory({
      label: "Test InUse ZZ",
      color: "#eeeeee",
    });
    if ("error" in res) throw new Error(res.error);
    created.push(res.data.id);
    const user = await prisma.users.findFirst({ select: { id: true } });
    if (!user) throw new Error("no user in test DB");
    const entry = await prisma.work_plan_entries.create({
      data: {
        user_id: user.id,
        date_from: new Date("2026-06-10T00:00:00.000Z"),
        date_to: new Date("2026-06-10T00:00:00.000Z"),
        category: res.data.key,
        note: "test in-use",
        created_by: user.id,
      },
    });
    try {
      const del = await deletePlanCategory(res.data.id);
      expect("error" in del).toBe(true);
      if ("error" in del) expect(del.status).toBe(409);
    } finally {
      await prisma.work_plan_entries.delete({ where: { id: entry.id } });
    }
  });
});
