import { afterAll, beforeEach, expect, mock, test } from "bun:test";
import { eq } from "drizzle-orm";
import { assertTestDb } from "@/lib/db/assert-test-db";
import { organization, user } from "@/lib/db/auth-schema";
import { client, db } from "@/lib/db/client";
import { contentItems, gscPageSnapshots, sites } from "@/lib/db/domain-schema";

// Mock next/cache so revalidatePath doesn't throw outside a Next.js request context.
mock.module("next/cache", () => ({
  revalidatePath: () => {},
  revalidateTag: () => {},
  updateTag: () => {},
  unstable_cache: (fn: unknown) => fn,
}));

const SITE = "stats-site";
const ORG = "stats-org";
const USR = "stats-user";

/** Insert one daily page snapshot for SITE. */
async function snap(
  page: string,
  date: string,
  clicks: number,
  impressions: number,
  position: number,
): Promise<void> {
  await db.insert(gscPageSnapshots).values({
    siteId: SITE,
    date,
    page,
    clicks,
    impressions,
    ctr: impressions ? clicks / impressions : 0,
    position,
  });
}

/** ISO date `off` days before today (the data window moves daily). */
const d = (off: number) =>
  new Date(Date.now() - off * 86400000).toISOString().slice(0, 10);

beforeEach(async () => {
  await assertTestDb();
  await db.delete(gscPageSnapshots).where(eq(gscPageSnapshots.siteId, SITE));
  await db.delete(contentItems).where(eq(contentItems.siteId, SITE));
  await db.delete(sites).where(eq(sites.id, SITE));
  await db.delete(organization).where(eq(organization.id, ORG));
  await db.delete(user).where(eq(user.id, USR));
  await db.insert(user).values({
    id: USR,
    name: "U",
    email: `stats-${Date.now()}@e.com`,
    emailVerified: true,
  });
  await db.insert(organization).values({
    id: ORG,
    name: "O",
    slug: `stats-${Date.now()}`,
    createdAt: new Date(),
  });
  await db.insert(sites).values({
    id: SITE,
    orgId: ORG,
    ownerUserId: USR,
    name: "S",
    url: "https://x.com",
    primaryLanguage: "fr",
    targetCountries: ["FR"],
  });
});

afterAll(async () => {
  // NB: do NOT client.end() — shared singleton; closing breaks directory runs.
  void client;
});

test("aggregates current vs previous period with weighted position", async () => {
  await snap("https://x.com/a", d(2), 10, 100, 5);
  await snap("https://x.com/a", d(3), 20, 300, 10);
  await snap("https://x.com/a", d(9), 40, 200, 4); // previous period
  const { getPageStats } = await import("./stats");
  const { rows, kpis } = await getPageStats(SITE, 7);
  const a = rows.find((r) => r.page === "https://x.com/a");
  expect(a).toBeDefined();
  if (!a) throw new Error("row missing");
  expect(a.clicks).toBe(30);
  expect(a.impressions).toBe(400);
  expect(a.position).toBe(8.8); // (5*100+10*300)/400 = 8.75 → arrondi 8.8
  expect(a.prevClicks).toBe(40);
  expect(a.prevPosition).toBe(4);
  expect(a.spark.length).toBe(7);
  expect(a.spark.reduce((s, v) => s + v, 0)).toBe(30);
  expect(kpis.clicks).toBe(30);
  expect(kpis.prevClicks).toBe(40);
});

test("prev null when no previous data; content item matched by normalized url", async () => {
  await snap("https://x.com/b/", d(2), 5, 50, 7);
  await db.insert(contentItems).values({
    id: "ci-b",
    siteId: SITE,
    title: "Article B",
    status: "published",
    lang: "fr",
    country: "FR",
    url: "https://X.com/b",
  });
  const { getPageStats } = await import("./stats");
  const { rows } = await getPageStats(SITE, 7);
  const b = rows.find((r) => r.page === "https://x.com/b/");
  expect(b).toBeDefined();
  if (!b) throw new Error("row missing");
  expect(b.prevClicks).toBeNull();
  expect(b.contentItemId).toBe("ci-b");
  expect(b.contentTitle).toBe("Article B");
  expect(b.dropping).toBe(false); // période précédente vide → garde
});
