import { expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import {
  metricsFromMangools,
  serpSnapshotFromMangools,
  trendFromMangools,
} from "./normalize";
import type {
  MangoolsKeywordItem,
  MangoolsRelatedResponse,
  MangoolsSerpResponse,
} from "./types";

function loadFixture<T>(name: string): T {
  return JSON.parse(
    readFileSync(join(import.meta.dir, "fixtures", name), "utf8"),
  ) as T;
}

const related = loadFixture<MangoolsRelatedResponse>("fixture-related.json");
const serps = loadFixture<MangoolsSerpResponse>("fixture-serps.json");
// kw "bague de fiançailles" : sv 84700, seo 31, cpc 1.78, msv historique long.
const bague = related.keywords[0];
// kw "bague de fiancaille diamant 2015" : fenêtre 13 pts à 0 → base nulle → null.
const bagueDiamant = related.keywords[1];
// kw "bague de fiancaille femme or blanc pas cher" : fenêtre 2025-06 (10) →
// 2026-06 (0), même mois → (0 - 10) / 10 × 100 = -100.
const bagueOrBlanc = related.keywords[2];

const NOW = "2026-07-25T00:00:00.000Z";

test("metricsFromMangools — golden sur 'bague de fiançailles'", () => {
  expect(metricsFromMangools(bague, NOW)).toEqual({
    volume: 84700,
    kd: 31,
    cpc: 1.78,
    growthPct: -18.2, // YoY même-mois, 13 pts : 2025-06 (90500) → 2026-06 (74000)
    competitiveDensity: null,
    numberOfResults: null,
    updatedAt: NOW,
  });
});

test("growthFromMsv — YoY même-mois : base 0 sur la fenêtre → null", () => {
  // "bague de fiancaille diamant 2015" : 13 derniers points tous à 0, 1er point
  // (2025-06) = base 0 → growthPct null.
  expect(metricsFromMangools(bagueDiamant, NOW).growthPct).toBeNull();
});

test("growthFromMsv — YoY même-mois : (0 - 10) / 10 → -100", () => {
  // "bague de fiancaille femme or blanc pas cher" : 2025-06 (10) → 2026-06 (0),
  // même mois calendaire, base non nulle → -100.
  expect(metricsFromMangools(bagueOrBlanc, NOW).growthPct).toBe(-100);
});

test("growthFromMsv — gap : 13 points mais bornes de mois ≠ → null", () => {
  // 13 points dont un gap (mois 5 manquant) : 1er point 2025-05 (mois 4),
  // dernier 2026-06 (mois 5) → mois différents → refus (jamais comparer deux
  // mois calendaires distincts en silence).
  const gapped: MangoolsKeywordItem = {
    _id: "gap",
    kw: "gap",
    lid: 0,
    country: null,
    sv: 500,
    seo: 20,
    cpc: 0.5,
    ppc: 0,
    msv: [
      [2025, 4, 500], // 2025-05 (mois 4) — 1er point de la fenêtre
      [2025, 6, 500], // gap : 2025-06 (mois 5) manquant
      [2025, 7, 500],
      [2025, 8, 500],
      [2025, 9, 500],
      [2025, 10, 500],
      [2025, 11, 500],
      [2026, 0, 500],
      [2026, 1, 500],
      [2026, 2, 500],
      [2026, 3, 500],
      [2026, 4, 500],
      [2026, 5, 600], // 2026-06 (mois 5) — dernier point
    ],
  };
  expect(metricsFromMangools(gapped, NOW).growthPct).toBeNull();
});

test("metricsFromMangools — champs null → 0, updatedAt = now", () => {
  const empty: MangoolsKeywordItem = {
    _id: "x",
    kw: "vide",
    lid: 0,
    country: null,
    sv: null,
    seo: null,
    cpc: null,
    ppc: null,
    msv: null,
  };
  expect(metricsFromMangools(empty, NOW)).toEqual({
    volume: 0,
    kd: 0,
    cpc: 0,
    growthPct: null,
    competitiveDensity: null,
    numberOfResults: null,
    updatedAt: NOW,
  });
});

test("metricsFromMangools — <13 points de msv → growthPct null", () => {
  const short: MangoolsKeywordItem = {
    _id: "y",
    kw: "court",
    lid: 0,
    country: null,
    sv: 100,
    seo: 10,
    cpc: 0.5,
    ppc: 0,
    msv: [
      [2025, 6, 100],
      [2025, 7, 110],
      [2025, 8, 120],
    ],
  };
  expect(metricsFromMangools(short, NOW).growthPct).toBeNull();
});

test("trendFromMangools — golden : 24 mois max, chronologique", () => {
  const trend = trendFromMangools(bague);
  expect(trend).toHaveLength(24);
  expect(trend[0]).toEqual({ month: "2024-07", volume: 110000 });
  expect(trend[23]).toEqual({ month: "2026-06", volume: 74000 });
  // ordre chronologique strict (mois croissants)
  for (let i = 1; i < trend.length; i++) {
    expect(trend[i].month > trend[i - 1].month).toBe(true);
  }
});

test("trendFromMangools — msv null → []", () => {
  const empty: MangoolsKeywordItem = {
    _id: "z",
    kw: "vide",
    lid: 0,
    country: null,
    sv: null,
    seo: null,
    cpc: null,
    ppc: null,
    msv: null,
  };
  expect(trendFromMangools(empty)).toEqual([]);
});

test("serpSnapshotFromMangools — golden sur fixture serps", () => {
  const snap = serpSnapshotFromMangools(serps, NOW);
  expect(snap.source).toBe("mangools");
  expect(snap.importedAt).toBe(NOW);
  expect(snap.features).toEqual(["PEOPLE_ALSO_ASK"]);
  // 8 organiques, pinterest.com filtré (non-concurrent) → 7 restants.
  expect(snap.competitors).toHaveLength(7);
  expect(snap.competitors.map((c) => c.domain)).not.toContain("pinterest.com");
});

test("serpSnapshotFromMangools — cape au top 10 (15 organiques → 10)", () => {
  const many: MangoolsSerpResponse = {
    kw: "test",
    location_id: 0,
    items: Array.from({ length: 15 }, (_, idx) => ({
      type: "ORGANIC",
      serpRank: idx + 1,
      domain: `example${idx + 1}.com`,
      url: `https://example${idx + 1}.com/`,
    })),
  };
  const snap = serpSnapshotFromMangools(many, NOW);
  expect(snap.competitors).toHaveLength(10);
  expect(snap.competitors.map((c) => c.position)).toEqual([
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
  ]);
});

test("serpSnapshotFromMangools — ORGANIC sans serpRank exclu", () => {
  const resp: MangoolsSerpResponse = {
    kw: "test",
    location_id: 0,
    items: [
      { type: "ORGANIC", domain: "no-rank.com", url: "https://no-rank.com/" },
      {
        type: "ORGANIC",
        serpRank: 1,
        domain: "ranked.com",
        url: "https://ranked.com/",
      },
    ],
  };
  const snap = serpSnapshotFromMangools(resp, NOW);
  expect(snap.competitors).toHaveLength(1);
  expect(snap.competitors[0].domain).toBe("ranked.com");
});

test("serpSnapshotFromMangools — 1er organique histoiredor.com", () => {
  const snap = serpSnapshotFromMangools(serps, NOW);
  expect(snap.competitors[0]).toEqual({
    position: 1,
    domain: "histoiredor.com",
    url: "https://www.histoiredor.com/fr_FR/c/bagues/par-type/bagues-de-fiancailles/",
    backlinks: 8, // majestic ExtBackLinks
    refDomains: 5, // majestic RefDomains
    pageAuthority: 40, // moz upa (URL Page Authority)
    searchTraffic: 0,
    urlKeywords: 0,
    title: null,
    metaDescription: null,
    lang: null,
    images: null,
    imagesWithoutAlt: null,
    imageAlts: null,
    missingAltSrcs: null,
    hasJsonLd: null,
    jsonLdTypes: null,
    crawledAt: null,
  });
});
