// Integration test — gated on DB_TESTS=1, run against the *_test DB only.
// Mirrors the harness in actions.test.ts: server-only stub, assertTestDb guard,
// table cleanup + site seed in beforeEach (so FK constraints pass).
import { beforeEach, describe, expect, mock, test } from "bun:test";

// migrate-slugs pulls in the db client; nothing imports "server-only" here, but
// keep the stub to match the sibling suite's environment exactly.
mock.module("server-only", () => ({}));

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

describe.skipIf(!RUN_DB)("slug migration (postgres)", () => {
  let db: typeof import("@/lib/db/client").db;
  let schema: typeof import("@/lib/db/domain-schema");
  let migrateSlugs: typeof import("./migrate-slugs").migrateSlugs;
  let eq: typeof import("drizzle-orm").eq;

  const SITE = "lustre-lab";

  async function seedKeyword(
    id: string,
    term: string,
    extra: Record<string, unknown> = {},
  ) {
    await db.insert(schema.keywords).values({
      id,
      siteId: SITE,
      term,
      lang: "fr",
      country: "FR",
      status: "idea",
      primarySource: "gsc",
      sources: ["gsc"],
      ...(extra as Record<string, never>),
    });
  }

  beforeEach(async () => {
    ({ db } = await import("@/lib/db/client"));
    schema = await import("@/lib/db/domain-schema");
    ({ migrateSlugs } = await import("./migrate-slugs"));
    ({ eq } = await import("drizzle-orm"));
    const { assertTestDb } = await import("@/lib/db/assert-test-db");
    await assertTestDb();
    // Children first (defensive; FK cascade would also handle it), then parents.
    await db.delete(schema.keywordShortlist);
    await db.delete(schema.keywordTargets);
    await db.delete(schema.keywordExclusions);
    await db.delete(schema.keywords);
    await db.delete(schema.sites);
    await db.insert(schema.sites).values({
      id: SITE,
      orgId: "demo-org",
      ownerUserId: "seed-owner",
      name: "Lustre Lab",
      url: "https://lustrelab.com",
      primaryLanguage: "fr",
      languages: [],
      targetCountries: ["FR"],
    });
  });

  test("merges drop-accent + transliterated collisions into one canonical keyword", async () => {
    const LOSER = `${SITE}-lectricit`; // old drop-accent slug of "électricité"
    const CANON = `${SITE}-electricite`; // both terms transliterate here

    await seedKeyword(LOSER, "électricité", {
      status: "watch",
      stages: ["research"],
      metrics: { gsc: { volume: 10 } },
    });
    await seedKeyword(CANON, "electricite", {
      status: "active",
      sources: ["ahrefs"],
      metrics: { ahrefs: { volume: 20 } },
    });

    // FK children on the LOSER.
    await db.insert(schema.keywordTargets).values({
      keywordId: LOSER,
      country: "FR",
      status: "candidate",
      siteId: SITE,
    });
    await db.insert(schema.keywordShortlist).values({
      siteId: SITE,
      keywordId: LOSER,
    });

    await migrateSlugs();

    const rows = await db
      .select()
      .from(schema.keywords)
      .where(eq(schema.keywords.siteId, SITE));
    expect(rows).toHaveLength(1);
    const k = rows[0];
    expect(k.id).toBe(CANON);
    expect(k.term).toBe("électricité"); // accented preferred
    expect(k.status).toBe("active"); // most-advanced
    expect(k.stages).toContain("research");
    expect(k.metrics).toEqual({
      gsc: { volume: 10 },
      ahrefs: { volume: 20 },
    } as never);

    // FK children repointed to the canonical id, none left on the loser.
    const targets = await db.select().from(schema.keywordTargets);
    expect(targets).toHaveLength(1);
    expect(targets[0].keywordId).toBe(CANON);
    expect(targets[0].country).toBe("FR");

    const short = await db.select().from(schema.keywordShortlist);
    expect(short).toHaveLength(1);
    expect(short[0].keywordId).toBe(CANON);

    const loserTargets = await db
      .select()
      .from(schema.keywordTargets)
      .where(eq(schema.keywordTargets.keywordId, LOSER));
    expect(loserTargets).toHaveLength(0);
    const loserShort = await db
      .select()
      .from(schema.keywordShortlist)
      .where(eq(schema.keywordShortlist.keywordId, LOSER));
    expect(loserShort).toHaveLength(0);
  });

  test("pure rename repoints children (no collision)", async () => {
    const OLD = `${SITE}-nergie`; // old drop-accent slug of "énergie"
    const NEW = `${SITE}-energie`; // transliterated

    await seedKeyword(OLD, "énergie");
    await db.insert(schema.keywordShortlist).values({
      siteId: SITE,
      keywordId: OLD,
    });

    await migrateSlugs();

    const rows = await db
      .select()
      .from(schema.keywords)
      .where(eq(schema.keywords.siteId, SITE));
    expect(rows).toHaveLength(1);
    expect(rows[0].id).toBe(NEW);

    const oldRow = await db
      .select()
      .from(schema.keywords)
      .where(eq(schema.keywords.id, OLD));
    expect(oldRow).toHaveLength(0);

    const short = await db.select().from(schema.keywordShortlist);
    expect(short).toHaveLength(1);
    expect(short[0].keywordId).toBe(NEW);
  });

  test("idempotent: a second run is a no-op", async () => {
    // term already canonical ("audit" -> "audit"), id already correct.
    await seedKeyword(`${SITE}-audit`, "audit");

    await migrateSlugs();
    const second = await migrateSlugs();
    expect(second.groups).toBe(0);
  });
});
