// Integration test — gated on DB_TESTS=1, run against the *_test DB only.
import { beforeEach, describe, expect, mock, test } from "bun:test";
import { MAX_THEME_LABEL_LENGTH } from "./types";

// actions/queries import "server-only" (throws outside Next) — stub it.
mock.module("server-only", () => ({}));
// the tenancy guard needs a session (headers()) — stub it; site-scoping is
// covered at the SQL level by these tests and by assert-site.test.ts.
mock.module("@/lib/sites/assert-site", () => ({
  siteBelongsToOrg: async () => true,
  assertSiteInActiveOrg: async () => {},
  assertKeywordInActiveOrg: async () => "mock-site",
  assertContentItemInActiveOrg: async () => "mock-site",
}));

const RUN_DB = process.env.DB_TESTS === "1";

describe.skipIf(!RUN_DB)("keyword theme actions (postgres)", () => {
  let db: typeof import("@/lib/db/client").db;
  let schema: typeof import("@/lib/db/domain-schema");
  let actions: typeof import("./actions");

  const SITE_A = "lustre-lab";
  const SITE_B = "atelier-forge";

  beforeEach(async () => {
    ({ db } = await import("@/lib/db/client"));
    schema = await import("@/lib/db/domain-schema");
    actions = await import("./actions");
    const { assertTestDb } = await import("@/lib/db/assert-test-db");
    await assertTestDb();
    await db.delete(schema.keywords);
    await db.delete(schema.keywordThemes);
    await db.delete(schema.sites);
    await db.insert(schema.sites).values([
      {
        id: SITE_A,
        orgId: "demo-org",
        ownerUserId: "seed-owner",
        name: "Lustre Lab",
        url: "https://lustrelab.com",
        primaryLanguage: "fr",
        languages: [],
        targetCountries: ["FR"],
      },
      {
        id: SITE_B,
        orgId: "demo-org",
        ownerUserId: "seed-owner",
        name: "Atelier Forge",
        url: "https://atelierforge.com",
        primaryLanguage: "fr",
        languages: [],
        targetCountries: ["FR"],
      },
    ]);
  });

  test("createThemeAction inserts a theme with a slugified slug", async () => {
    const res = await actions.createThemeAction(SITE_A, "Pierres Rares");
    expect(res).toEqual({ slug: "pierres-rares", label: "Pierres Rares" });
    const themes = await actions.fetchThemesForSiteAction(SITE_A);
    expect(themes).toEqual([{ slug: "pierres-rares", label: "Pierres Rares" }]);
  });

  test("createThemeAction is idempotent on slug", async () => {
    await actions.createThemeAction(SITE_A, "Pierres Rares");
    await actions.createThemeAction(SITE_A, "Pierres Rares");
    const themes = await actions.fetchThemesForSiteAction(SITE_A);
    expect(themes).toHaveLength(1);
  });

  test("un thème existant est RÉUTILISÉ, son libellé n'est pas écrasé", async () => {
    // Deux saisies qui convergent sur le même slug : la seconde doit rendre le
    // thème déjà là (le champ de thème de l'étape 3 s'y rattache) sans
    // renommer ce que l'utilisateur avait posé.
    await actions.createThemeAction(SITE_A, "Pierres Rares");
    const again = await actions.createThemeAction(SITE_A, "pierres  rares");
    expect(again).toEqual({ slug: "pierres-rares", label: "pierres  rares" });
    const themes = await actions.fetchThemesForSiteAction(SITE_A);
    expect(themes).toEqual([{ slug: "pierres-rares", label: "Pierres Rares" }]);
  });

  test("createThemeAction refuse un libellé vide", async () => {
    expect(await actions.createThemeAction(SITE_A, "   ")).toBeNull();
    expect(await actions.fetchThemesForSiteAction(SITE_A)).toHaveLength(0);
  });

  test("createThemeAction refuse un libellé au-delà de la borne", async () => {
    // MÊME borne que le chemin d'import (`theme-store`) : un libellé peut
    // entrer par les deux, il doit y être traité pareil. Au-delà on ÉCARTE
    // plutôt que tronquer — une troncature fabriquerait des collisions
    // silencieuses entre thèmes distincts.
    const tooLong = "x".repeat(MAX_THEME_LABEL_LENGTH + 1);
    expect(await actions.createThemeAction(SITE_A, tooLong)).toBeNull();
    expect(await actions.fetchThemesForSiteAction(SITE_A)).toHaveLength(0);
    // Pile sur la borne : accepté.
    const atLimit = "y".repeat(MAX_THEME_LABEL_LENGTH);
    expect(await actions.createThemeAction(SITE_A, atLimit)).not.toBeNull();
    expect(await actions.fetchThemesForSiteAction(SITE_A)).toHaveLength(1);
  });

  test("themes are site-scoped", async () => {
    await actions.createThemeAction(SITE_A, "Pierres Rares");
    const a = await actions.fetchThemesForSiteAction(SITE_A);
    const b = await actions.fetchThemesForSiteAction(SITE_B);
    expect(a).toHaveLength(1);
    expect(b).toHaveLength(0);
  });

  test("renameThemeAction updates the label", async () => {
    await actions.createThemeAction(SITE_A, "Pierres Rares");
    await actions.renameThemeAction(SITE_A, "pierres-rares", "Gemmes");
    const themes = await actions.fetchThemesForSiteAction(SITE_A);
    expect(themes).toEqual([{ slug: "pierres-rares", label: "Gemmes" }]);
  });
});
