// src/hooks/use-enrich-polling.test.ts — logique pure de continuation du poll.
// Le repo n'a pas de harness de test de hooks (pas de @testing-library/react) ;
// la décision « faut-il continuer à poller » est donc extraite en fonction pure
// `hasActiveEnrich`, testée ici en isolation. Le module importe l'action de
// lecture (server-only) : on la stub pour éviter d'entraîner le graphe DB.
// `hasActiveEnrich` est importé du module de logique PURE (server-free) et non
// du hook : importer le hook entraînerait l'action serveur → graphe DB, ce qui
// figerait tôt le binding de `assert-site` et fausserait l'ordre de chargement
// des autres suites (pollution inter-fichiers en un même process bun).
import { describe, expect, test } from "bun:test";
import type { KeywordEnrichment } from "@/lib/keywords/types";
import {
  didEnrichSettle,
  hasActiveEnrich,
  isStaleResponse,
} from "./enrich-polling-logic";

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

describe("hasActiveEnrich", () => {
  test("map vide → pas de poll", () => {
    expect(hasActiveEnrich({})).toBe(false);
  });

  test("uniquement done/error → pas de poll (arrêt auto)", () => {
    const statuses: Record<string, KeywordEnrichment> = {
      a: { mangools: { status: "done", at } },
      b: { seranking: { status: "error", at, error: "not_found" } },
    };
    expect(hasActiveEnrich(statuses)).toBe(false);
  });

  test("un statut queued → poll", () => {
    expect(hasActiveEnrich({ a: { mangools: { status: "queued", at } } })).toBe(
      true,
    );
  });

  test("un statut syncing → poll", () => {
    expect(
      hasActiveEnrich({ a: { seranking: { status: "syncing", at } } }),
    ).toBe(true);
  });

  test("actif détecté toutes sources confondues (une seule source en vol suffit)", () => {
    const statuses: Record<string, KeywordEnrichment> = {
      a: {
        mangools: { status: "done", at },
        seranking: { status: "syncing", at },
      },
    };
    expect(hasActiveEnrich(statuses)).toBe(true);
  });

  test("entrée sans état ({}) → pas de poll", () => {
    expect(hasActiveEnrich({ a: {} })).toBe(false);
  });
});

describe("didEnrichSettle (transition active→inactive)", () => {
  test("actif → inactif → true (un enrichissement vient de se terminer)", () => {
    expect(didEnrichSettle(true, false)).toBe(true);
  });

  test("inactif → inactif → false (aucune transition : pas de refresh en chaîne)", () => {
    // C'est la garde anti-boucle : après le refresh, prev repasse false ; tant
    // que next reste false, on ne re-fire jamais.
    expect(didEnrichSettle(false, false)).toBe(false);
  });

  test("inactif → actif → false (un enrichissement démarre, pas de refresh)", () => {
    expect(didEnrichSettle(false, true)).toBe(false);
  });

  test("actif → actif → false (toujours en vol)", () => {
    expect(didEnrichSettle(true, true)).toBe(false);
  });

  test("séquence complète : la transition n'est détectée qu'UNE fois", () => {
    // Simule le cycle d'un ref: prev part de false, on suit active au fil des
    // renders. Le refresh (settle) ne doit fire qu'au render où active tombe.
    const activeByRender = [false, true, true, false, false, false];
    let prev = false;
    const fired: number[] = [];
    activeByRender.forEach((active, i) => {
      if (didEnrichSettle(prev, active)) fired.push(i);
      prev = active;
    });
    expect(fired).toEqual([3]); // exactement un refresh, au render 3
  });
});

describe("isStaleResponse (poll out-of-order)", () => {
  test("seq === latest → non obsolète (on applique la réponse)", () => {
    expect(isStaleResponse(3, 3)).toBe(false);
  });

  test("seq < latest → obsolète (une requête plus récente est partie) → ignorée", () => {
    expect(isStaleResponse(2, 5)).toBe(true);
  });

  test("réponses dans le désordre : seule la plus récente est appliquée", () => {
    // Deux requêtes partent (seq 1 puis 2) ; la 1 revient APRÈS la 2. À la
    // réception de la 1, latest vaut déjà 2 → sa réponse est jetée.
    const latest = 2;
    expect(isStaleResponse(2, latest)).toBe(false); // la récente s'applique
    expect(isStaleResponse(1, latest)).toBe(true); // la lente est ignorée
  });
});
