// src/lib/backlink/overview.test.ts — integration, gated on DB_TESTS=1.
import { beforeEach, describe, expect, mock, test } from "bun:test";

mock.module("server-only", () => ({}));

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

describe.skipIf(!RUN_DB)("backlink overview aggregates (postgres)", () => {
  let db: typeof import("@/lib/db/client").db;
  let schema: typeof import("@/lib/db/domain-schema");
  const ORG = "demo-org";
  const SITE_A = "wt-ov-a";
  const SITE_B = "wt-ov-b";
  const SITE_C = "wt-ov-c";

  async function insertSource(
    id: string,
    overrides: Record<string, unknown> = {},
  ) {
    await db.insert(schema.linkSources).values({
      id,
      orgId: ORG,
      platform: id,
      url: `https://${id.toLowerCase()}.com`,
      domainNorm: `${id.toLowerCase()}.com`,
      kind: "blog",
      da: 40,
      effort: "low",
      linkType: "dofollow",
      language: "fr",
      ...overrides,
    } as never);
  }

  beforeEach(async () => {
    ({ db } = await import("@/lib/db/client"));
    schema = await import("@/lib/db/domain-schema");
    const { assertTestDb } = await import("@/lib/db/assert-test-db");
    const { inArray } = await import("drizzle-orm");
    await assertTestDb();
    await db.delete(schema.backlinkOutreach);
    await db.delete(schema.backlinks);
    await db.delete(schema.linkSources);
    await db.delete(schema.domainRatings);
    await db
      .delete(schema.sites)
      .where(inArray(schema.sites.id, [SITE_A, SITE_B, SITE_C]));
    await db.insert(schema.sites).values(
      [SITE_A, SITE_B, SITE_C].map((id) => ({
        id,
        orgId: ORG,
        ownerUserId: "seed-owner",
        name: id,
        url: `https://${id}.com`,
        primaryLanguage: "fr",
        languages: [],
        targetCountries: ["FR"],
      })),
    );
  });

  test("pool: counts, byKind/byLanguage triés, drBuckets (DR partagé + fallback DA)", async () => {
    await insertSource("S1", { kind: "blog", language: "fr" }); // dr 90 → 81–100
    await insertSource("S2", { kind: "blog", language: "en", da: 10 }); // → 0–20
    await insertSource("S3", { kind: "annuaire", language: "fr", da: null }); // → N/A
    await db
      .insert(schema.domainRatings)
      .values({ domainNorm: "s1.com", dr: 90 });

    const { getBacklinkOverview } = await import("./overview");
    const ov = await getBacklinkOverview(ORG, SITE_A);
    expect(ov.pool.sources).toBe(3);
    expect(ov.pool.byKind[0]).toEqual({ kind: "blog", count: 2 });
    expect(ov.pool.byLanguage[0]).toEqual({ language: "fr", count: 2 });
    const byLabel = Object.fromEntries(
      ov.pool.drBuckets.map((b) => [b.label, b.count]),
    );
    expect(byLabel["81–100"]).toBe(1);
    expect(byLabel["0–20"]).toBe(1);
    expect(byLabel["N/A"]).toBe(1);
  });

  test("risks: footprint ≥3 sites, toxic/lost site vs org, disavow site", async () => {
    await insertSource("S1");
    await insertSource("S2");
    await db.insert(schema.backlinks).values([
      { id: "b1", linkSourceId: "S1", siteId: SITE_A, status: "live" },
      { id: "b2", linkSourceId: "S1", siteId: SITE_B, status: "live" },
      { id: "b3", linkSourceId: "S1", siteId: SITE_C, status: "live" },
      {
        id: "b4",
        linkSourceId: "S2",
        siteId: SITE_A,
        status: "lost",
        toxic: true,
        disavow: true,
      },
      {
        id: "b5",
        linkSourceId: "S2",
        siteId: SITE_B,
        status: "lost",
        toxic: true,
      },
    ]);

    const { getBacklinkOverview } = await import("./overview");
    const ov = await getBacklinkOverview(ORG, SITE_A);
    expect(ov.risks.footprintDomains).toEqual([
      { domainNorm: "s1.com", siteCount: 3 },
    ]);
    expect(ov.risks.toxicSite).toBe(1);
    expect(ov.risks.toxicOrg).toBe(2);
    expect(ov.risks.lostSite).toBe(1);
    expect(ov.risks.lostOrg).toBe(2);
    expect(ov.risks.disavowSite).toBe(1);
  });

  test("campaign: compteurs par statut (clés complètes) + cadence", async () => {
    await insertSource("S1");
    await insertSource("S2");
    await db.insert(schema.backlinks).values([
      { id: "b1", linkSourceId: "S1", siteId: SITE_A, status: "prospect" },
      {
        id: "b2",
        linkSourceId: "S2",
        siteId: SITE_A,
        status: "live",
        firstSeen: new Date(),
      },
    ]);

    const { getBacklinkOverview } = await import("./overview");
    const ov = await getBacklinkOverview(ORG, SITE_A);
    expect(ov.campaign.counters).toEqual({
      prospect: 1,
      planned: 0,
      in_progress: 0,
      live: 1,
      lost: 0,
    });
    expect(ov.campaign.cadence.thisMonth).toBe(1);
    expect(ov.campaign.cadence.lastMonth).toBe(0);
  });

  test("opportunities: exclut les sources déjà prospectées par le site, top par score", async () => {
    await insertSource("S1", { language: "fr" }); // prospecté par A → exclu
    await insertSource("S2", { language: "fr", da: 80 }); // score 80
    await insertSource("S3", { language: "en", da: 90 }); // score 27 (0.3×90)
    await db.insert(schema.backlinks).values([
      { id: "b1", linkSourceId: "S1", siteId: SITE_A, status: "prospect" },
      { id: "b2", linkSourceId: "S2", siteId: SITE_B, status: "live" },
    ]);

    const { getBacklinkOverview } = await import("./overview");
    const ov = await getBacklinkOverview(ORG, SITE_A);
    expect(ov.opportunities.map((o) => o.id)).toEqual(["S2", "S3"]);
    expect(ov.opportunities[0]?.score).toBe(80);
  });

  test("activity: prospects créés + passages live, 5 plus récents desc", async () => {
    await insertSource("S1");
    await insertSource("S2");
    const old = new Date("2026-01-01T00:00:00Z");
    const mid = new Date("2026-03-01T00:00:00Z");
    const recent = new Date("2026-06-01T00:00:00Z");
    await db.insert(schema.backlinks).values([
      {
        id: "b1",
        linkSourceId: "S1",
        siteId: SITE_A,
        status: "live",
        createdAt: old,
        firstSeen: recent,
      },
      {
        id: "b2",
        linkSourceId: "S2",
        siteId: SITE_A,
        status: "prospect",
        createdAt: mid,
      },
      { id: "b3", linkSourceId: "S1", siteId: SITE_B, status: "prospect" },
    ]);

    const { getBacklinkOverview } = await import("./overview");
    const ov = await getBacklinkOverview(ORG, SITE_A);
    expect(ov.activity.map((e) => [e.type, e.platform])).toEqual([
      ["live", "S1"],
      ["prospect", "S2"],
      ["prospect", "S1"],
    ]);
    expect(ov.activity[0]?.at).toBe(recent.toISOString());
  });
});
