// src/lib/backlink/global-catalog.test.ts — catalogue global curé (god-mode).
// Integration tests — require Postgres + the test DB migrated. Skipped unless DB_TESTS=1.
import { beforeEach, describe, expect, mock, test } from "bun:test";
import { eq, inArray } from "drizzle-orm";

// server-only throws outside Next.js; stub it for the test runner.
mock.module("server-only", () => ({}));
// actions call revalidatePath, which throws outside a Next request scope.
mock.module("next/cache", () => ({
  revalidatePath: () => {},
  revalidateTag: () => {},
  updateTag: () => {},
  unstable_cache: (fn: unknown) => fn,
}));
// god-mode gate reads the session (headers()) — permissive stub; the gate itself
// is covered by super-admin unit tests. Mirror the module's full export surface.
mock.module("@/lib/auth/super-admin", () => ({
  emailInCsv: () => true,
  isSuperAdmin: () => true,
  isCurrentUserSuperAdmin: async () => true,
  assertSuperAdmin: async () => {},
}));
// actions resolve the active org/user from the Better Auth session — stub them
// to the demo org + seed owner so they run without a request.
mock.module("@/lib/active-org", () => ({
  getActiveOrgId: async () => "demo-org",
  getActiveUserId: async () => "seed-owner",
}));

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

describe.skipIf(!RUN_DB)("backlink global catalog (postgres)", () => {
  let db: typeof import("@/lib/db/client").db;
  let schema: typeof import("@/lib/db/domain-schema");
  let authSchema: typeof import("@/lib/db/auth-schema");

  const ORG = "demo-org"; // matches getActiveOrgId() stub
  const OTHER_ORG = "gc-other-org";
  /** Orgs created by createOrgForUser during a test — dropped in beforeEach too. */
  const createdOrgIds: string[] = [];

  async function insertSource(
    id: string,
    orgId: string,
    overrides: Record<string, unknown> = {},
  ) {
    await db.insert(schema.linkSources).values({
      id,
      orgId,
      platform: id,
      url: `https://${id.toLowerCase().replace(/[^a-z0-9]+/g, "-")}.com`,
      domainNorm: `${id.toLowerCase().replace(/[^a-z0-9]+/g, "-")}.com`,
      kind: "blog",
      da: 40,
      effort: "low",
      linkType: "dofollow",
      language: "en",
      ...overrides,
    } as never);
  }

  async function dropCreatedOrgs() {
    if (createdOrgIds.length === 0) return;
    // organization cascades to member + link_sources.
    await db
      .delete(authSchema.organization)
      .where(inArray(authSchema.organization.id, createdOrgIds));
    createdOrgIds.length = 0;
  }

  beforeEach(async () => {
    ({ db } = await import("@/lib/db/client"));
    schema = await import("@/lib/db/domain-schema");
    authSchema = await import("@/lib/db/auth-schema");
    const { assertTestDb } = await import("@/lib/db/assert-test-db");
    await assertTestDb();
    await dropCreatedOrgs();
    await db.delete(schema.backlinks);
    await db.delete(schema.linkSources);
    // Ensure the foreign org exists (FK target for link_sources.org_id).
    await db
      .insert(authSchema.organization)
      .values({
        id: OTHER_ORG,
        name: "GC Other Org",
        slug: "gc-other-org",
        createdAt: new Date(),
      })
      .onConflictDoNothing();
  });

  async function readSource(id: string) {
    const [row] = await db
      .select()
      .from(schema.linkSources)
      .where(eq(schema.linkSources.id, id));
    if (!row) throw new Error(`source ${id} absent`);
    return row;
  }

  test("setSourceGlobalAction flags a source of the active org", async () => {
    await insertSource("GC1", ORG);
    const { setSourceGlobalAction } = await import("./global-actions");

    expect(await setSourceGlobalAction("GC1", true)).toEqual({ ok: true });
    expect((await readSource("GC1")).global).toBe(true);

    expect(await setSourceGlobalAction("GC1", false)).toEqual({ ok: true });
    expect((await readSource("GC1")).global).toBe(false);
  });

  test("setSourceGlobalAction refuses a foreign-org source (row untouched)", async () => {
    await insertSource("GCX", OTHER_ORG);
    const { setSourceGlobalAction } = await import("./global-actions");

    expect(await setSourceGlobalAction("GCX", true)).toEqual({ ok: false });
    expect((await readSource("GCX")).global).toBe(false);
  });

  test("createOrgForUser copies the global catalog (re-namespaced ids, global=false)", async () => {
    // Catalog: one id with an org prefix, one without (both suffixOf branches).
    await insertSource("demo-org__alpha", ORG, { global: true });
    await insertSource("beta", ORG, { global: true });
    await insertSource("not-global", ORG); // must NOT be copied

    const { createOrgForUser } = await import("@/lib/auth/org-bootstrap");
    const newOrgId = await createOrgForUser("seed-owner", "GC Test Org");
    createdOrgIds.push(newOrgId);

    const rows = await db
      .select()
      .from(schema.linkSources)
      .where(eq(schema.linkSources.orgId, newOrgId));
    expect(rows.map((r) => r.id).sort()).toEqual([
      `${newOrgId}__alpha`,
      `${newOrgId}__beta`,
    ]);
    for (const r of rows) expect(r.global).toBe(false);
  });

  test("createOrgForUser falls back to fixtures when the catalog is empty", async () => {
    const { BACKLINK_SOURCES } = await import("./fixtures");
    const { createOrgForUser } = await import("@/lib/auth/org-bootstrap");
    const newOrgId = await createOrgForUser("seed-owner", "GC Fallback Org");
    createdOrgIds.push(newOrgId);

    const rows = await db
      .select()
      .from(schema.linkSources)
      .where(eq(schema.linkSources.orgId, newOrgId));
    expect(rows.length).toBe(BACKLINK_SOURCES.length);
    for (const r of rows.slice(0, 5)) expect(r.global).toBe(false);
  });

  test("getAllOrgsSourcesAction returns sources from every org with orgName", async () => {
    await insertSource("GC-mine", ORG);
    await insertSource("GC-theirs", OTHER_ORG);

    const { getAllOrgsSourcesAction } = await import("./global-actions");
    const all = await getAllOrgsSourcesAction();

    const mine = all.find((s) => s.id === "GC-mine");
    const theirs = all.find((s) => s.id === "GC-theirs");
    expect(mine).toBeDefined();
    expect(theirs).toBeDefined();
    expect(theirs?.orgName).toBe("GC Other Org");
    expect(mine?.orgName?.length).toBeGreaterThan(0);
    expect(new Set(all.map((s) => s.orgName)).size).toBeGreaterThanOrEqual(2);
  });
});
