import { describe, expect, it } from "vitest";
import type { ImportEntry } from "@/lib/imports";
import { importToTrackedJob, reconcileToasts, type TrackedJob } from "@/lib/job-toasts";

const JOB_ID = "11111111-1111-4111-8111-111111111111";
const OTHER_ID = "22222222-2222-4222-8222-222222222222";

function job(patch: Partial<TrackedJob> = {}): TrackedJob {
  return {
    id: JOB_ID,
    status: "running",
    title: "positions.csv",
    detail: null,
    href: null,
    error: null,
    ...patch,
  };
}

describe("reconcileToasts", () => {
  it("ouvre un toast de chargement pour un travail en file ou en cours", () => {
    const queued = job({ status: "queued" });
    const result = reconcileToasts(new Set(), [queued]);
    expect(result.actions).toEqual([{ kind: "loading", job: queued }]);
    expect([...result.tracked]).toEqual([JOB_ID]);
  });

  it("garde le même toast d'un tour à l'autre tant que le travail tourne", () => {
    const running = job();
    const result = reconcileToasts(new Set([JOB_ID]), [running]);
    expect(result.actions).toEqual([{ kind: "loading", job: running }]);
    expect([...result.tracked]).toEqual([JOB_ID]);
  });

  it("bascule en succès le travail suivi qui vient de se terminer", () => {
    const done = job({ status: "done" });
    const result = reconcileToasts(new Set([JOB_ID]), [done]);
    expect(result.actions).toEqual([{ kind: "success", job: done }]);
    expect([...result.tracked]).toEqual([]);
  });

  it("bascule en erreur le travail suivi qui vient d'échouer", () => {
    const failed = job({ status: "failed", error: "colonne manquante" });
    const result = reconcileToasts(new Set([JOB_ID]), [failed]);
    expect(result.actions).toEqual([{ kind: "error", job: failed }]);
    expect([...result.tracked]).toEqual([]);
  });

  it("ignore les travaux déjà terminés qui n'ont jamais été suivis", () => {
    // Sans cette règle, chaque chargement de page ferait pleuvoir un toast par import
    // historique de la première page du listing.
    const result = reconcileToasts(new Set(), [job({ status: "done" }), job({ id: OTHER_ID })]);
    expect(result.actions).toEqual([{ kind: "loading", job: job({ id: OTHER_ID }) }]);
    expect([...result.tracked]).toEqual([OTHER_ID]);
  });

  it("ferme le toast d'un travail suivi qui a disparu du listing", () => {
    // Il est sorti de la première page (imports plus récents) : rien ne dira jamais qu'il
    // s'est terminé, et un toast à durée infinie resterait là pour toujours.
    const result = reconcileToasts(new Set([JOB_ID]), []);
    expect(result.actions).toEqual([{ kind: "dismiss", id: JOB_ID }]);
    expect([...result.tracked]).toEqual([]);
  });

  it("ne rend jamais le Set reçu, pour que l'appelant compare deux états distincts", () => {
    const tracked = new Set([JOB_ID]);
    const result = reconcileToasts(tracked, [job()]);
    expect(result.tracked).not.toBe(tracked);
  });
});

const BASE_IMPORT = {
  entry: "import",
  id: JOB_ID,
  site_target_id: OTHER_ID,
  source: "semrush_positions",
  kind: "keywords",
  filename: "positions.csv",
  file_sha256: null,
  detected_format: "semrush_positions",
  detected_country: "fr",
  fetched_at: "2026-09-01T00:00:00Z",
  keyword_list_id: null,
  job_id: OTHER_ID,
  status: "running",
  error: null,
  report: null,
  created_at: "2026-09-01T00:00:00Z",
} satisfies ImportEntry;

describe("importToTrackedJob", () => {
  it("montre le nom du fichier et les lignes déjà lues pendant le traitement", () => {
    const tracked = importToTrackedJob(BASE_IMPORT, "site-1", { rows_read: 1200 });
    expect(tracked.title).toBe("positions.csv");
    expect(tracked.detail).toBe("1200 lignes lues");
    expect(tracked.status).toBe("running");
  });

  it("annonce la préparation tant que le worker n'a pas publié de progression", () => {
    expect(importToTrackedJob(BASE_IMPORT, "site-1", null).detail).toBe("Préparation du fichier…");
    expect(importToTrackedJob(BASE_IMPORT, "site-1", {}).detail).toBe("Préparation du fichier…");
  });

  it("dit ce qui se passe vraiment pour un import API : il n'y a pas de fichier", () => {
    //  Le nom de la liste KWFinder voyage dans `filename` — c'est ce qui distingue trois
    //  imports lancés d'un coup sur la même cible.
    const api = importToTrackedJob(
      { ...BASE_IMPORT, kind: "api", source: "mangools", filename: "ADVMFR USA Top" },
      "site-1",
      null,
    );
    expect(api.title).toBe("ADVMFR USA Top");
    expect(api.detail).toBe("Interrogation du fournisseur…");
  });

  it("résume le rapport une fois l'import terminé", () => {
    const done = importToTrackedJob(
      { ...BASE_IMPORT, status: "done", report: { rows_read: 10, rows_inserted: 9 } },
      "site-1",
      null,
    );
    expect(done.detail).toBe("10 lues · 9 insérées");
  });

  it("montre le message d'erreur de l'import échoué", () => {
    const failed = importToTrackedJob(
      { ...BASE_IMPORT, status: "failed", error: "colonne « keyword » absente" },
      "site-1",
      null,
    );
    expect(failed.detail).toBe("colonne « keyword » absente");
  });

  it("pointe vers le rapport de l'import quand le site est connu", () => {
    expect(importToTrackedJob(BASE_IMPORT, "site-1", null).href).toBe(
      `/sites/site-1/keywords/imports/${JOB_ID}`,
    );
  });

  it("n'invente pas de lien quand la cible n'appartient à aucun site connu", () => {
    // Cible supprimée, ou site hors de la première page du switcher : mieux vaut un toast
    // sans lien qu'un lien vers un 404.
    expect(importToTrackedJob(BASE_IMPORT, null, null).href).toBeNull();
  });

  it("retombe sur le libellé de l'adaptateur quand le fichier n'a pas de nom", () => {
    const tracked = importToTrackedJob({ ...BASE_IMPORT, filename: null }, "site-1", null);
    expect(tracked.title).toBe("Semrush — Organic Positions");
  });

  it("nomme un rattachement automatique, qui n'a ni fichier ni rapport d'import", () => {
    const tracked = importToTrackedJob(
      {
        entry: "map_reconcile",
        id: JOB_ID,
        site_target_id: OTHER_ID,
        status: "running",
        report: null,
        error: null,
        created_at: "2026-09-01T00:00:00Z",
      },
      "site-1",
      null,
    );
    expect(tracked.title).toBe("Rattachement automatique");
    expect(tracked.href).toBeNull();
    // Surtout pas « Préparation du fichier… » : un rattachement n'a pas de fichier.
    expect(tracked.detail).toBe("Rattachement en cours…");
  });

  it("ignore un compteur de lignes qui ne vient pas d'un import", () => {
    // `rows_read` est un compteur du pipeline CSV ; un autre type de travail publiant la
    // même clé ne doit pas se voir décrit en « lignes lues ».
    const tracked = importToTrackedJob(
      {
        entry: "map_reconcile",
        id: JOB_ID,
        site_target_id: null,
        status: "running",
        report: null,
        error: null,
        created_at: "2026-09-01T00:00:00Z",
      },
      "site-1",
      { rows_read: 40 },
    );
    expect(tracked.detail).toBe("Rattachement en cours…");
  });
});
