// src/lib/onboarding/activation/steps.test.ts
import { describe, expect, it } from "bun:test";
import {
  ACTIVATION_STEPS,
  canEnterGscStep,
  canEnterKeywordsStep,
  canEnterSiteStep,
  checklistSteps,
  doneCount,
  firstIncompleteStep,
  requiredComplete,
  WIZARD_STEP_IDS,
} from "./steps";
import {
  ACTIVATION_STEP_IDS,
  type ActivationFlags,
  type ActivationSnapshot,
} from "./types";

const none: ActivationFlags = {
  "create-site": false,
  "add-keywords": false,
  "enrich-keywords": false,
  "target-country": false,
  "connect-gsc": false,
  "invite-teammate": false,
};
const all: ActivationFlags = {
  "create-site": true,
  "add-keywords": true,
  "enrich-keywords": true,
  "target-country": true,
  "connect-gsc": true,
  "invite-teammate": true,
};

describe("ACTIVATION_STEPS", () => {
  it("expose les six étapes dans l'ordre canonique", () => {
    expect(ACTIVATION_STEPS.map((s) => s.id)).toEqual([
      "create-site",
      "connect-gsc",
      "add-keywords",
      "enrich-keywords",
      "target-country",
      "invite-teammate",
    ]);
  });

  it("couvre exactement ACTIVATION_STEP_IDS", () => {
    expect(ACTIVATION_STEPS.map((s) => s.id).sort()).toEqual(
      [...ACTIVATION_STEP_IDS].sort(),
    );
  });

  it("déclare requises trois étapes ; connect-gsc et enrich-keywords restent hors gate malgré leur place dans le parcours guidé", () => {
    expect(ACTIVATION_STEPS.filter((s) => s.required).map((s) => s.id)).toEqual(
      ["create-site", "add-keywords", "target-country"],
    );
    expect(WIZARD_STEP_IDS).toContain("connect-gsc");
    expect(WIZARD_STEP_IDS).toContain("enrich-keywords");
  });

  it("n'envoie jamais un CTA vers une section gatée en production", () => {
    for (const step of ACTIVATION_STEPS) {
      expect(step.href.startsWith("/content")).toBe(false);
      expect(step.href).not.toBe("/backlink/overview");
    }
  });
});

describe("WIZARD_STEP_IDS", () => {
  it("est le chemin critique, Search Console et enrichissement compris, dans l'ordre du wizard", () => {
    expect(WIZARD_STEP_IDS).toEqual([
      "create-site",
      "connect-gsc",
      "add-keywords",
      "enrich-keywords",
      "target-country",
    ]);
  });

  it("compte cinq étapes, Search Console en deuxième", () => {
    expect(WIZARD_STEP_IDS).toHaveLength(5);
    expect(WIZARD_STEP_IDS[1]).toBe("connect-gsc");
  });

  it("suit l'ordre canonique du catalogue", () => {
    const canonical = ACTIVATION_STEPS.map((s) => s.id).filter((id) =>
      (WIZARD_STEP_IDS as readonly string[]).includes(id),
    );
    expect(canonical).toEqual([...WIZARD_STEP_IDS]);
  });
});

describe("checklistSteps", () => {
  it("affiche les étapes requises avant les latérales", () => {
    expect(checklistSteps().map((s) => s.id)).toEqual([
      "create-site",
      "add-keywords",
      "target-country",
      "connect-gsc",
      "enrich-keywords",
      "invite-teammate",
    ]);
  });

  it("préserve l'ordre canonique à l'intérieur de chaque groupe", () => {
    const ranksOf = (required: boolean) =>
      checklistSteps()
        .filter((s) => s.required === required)
        .map((s) => ACTIVATION_STEPS.findIndex((c) => c.id === s.id));
    for (const required of [true, false]) {
      const ranks = ranksOf(required);
      expect(ranks).toEqual([...ranks].sort((a, b) => a - b));
    }
  });

  it("couvre exactement les mêmes étapes que le catalogue", () => {
    expect(
      checklistSteps()
        .map((s) => s.id)
        .sort(),
    ).toEqual(ACTIVATION_STEPS.map((s) => s.id).sort());
  });
});

describe("canEnterSiteStep", () => {
  const snapshot = (over: Partial<ActivationSnapshot> = {}) =>
    ({
      flags: none,
      dismissed: false,
      hasAccessibleSite: false,
      canCreateSite: true,
      ...over,
    }) satisfies ActivationSnapshot;

  it("ouvre l'étape au propriétaire d'une org sans site", () => {
    expect(canEnterSiteStep(snapshot())).toBe(true);
  });

  it("la refuse à qui ne peut pas créer de site (createSiteAction lèverait)", () => {
    expect(canEnterSiteStep(snapshot({ canCreateSite: false }))).toBe(false);
  });

  it("la referme dès que le site existe", () => {
    expect(
      canEnterSiteStep(snapshot({ flags: { ...none, "create-site": true } })),
    ).toBe(false);
  });

  it("la refuse sans snapshot : une dérivation en échec ne prouve aucun droit", () => {
    expect(canEnterSiteStep(null)).toBe(false);
  });

  it("ne dépend d'aucun autre drapeau que create-site", () => {
    expect(
      canEnterSiteStep(
        snapshot({ flags: { ...all, "create-site": false }, dismissed: true }),
      ),
    ).toBe(true);
  });
});

describe("canEnterGscStep", () => {
  const snapshot = (over: Partial<ActivationSnapshot> = {}) =>
    ({
      flags: none,
      dismissed: false,
      hasAccessibleSite: true,
      canCreateSite: true,
      ...over,
    }) satisfies ActivationSnapshot;

  it("ouvre l'étape dès qu'un site est accessible et non mappé", () => {
    expect(canEnterGscStep(snapshot())).toBe(true);
  });

  it("la referme une fois la propriété mappée", () => {
    expect(
      canEnterGscStep(snapshot({ flags: { ...none, "connect-gsc": true } })),
    ).toBe(false);
  });

  it("la refuse sans site accessible : le mapping n'aurait pas de cible", () => {
    expect(canEnterGscStep(snapshot({ hasAccessibleSite: false }))).toBe(false);
  });

  it("la refuse sans snapshot plutôt que d'appeler Google à l'aveugle", () => {
    expect(canEnterGscStep(null)).toBe(false);
  });

  it("ne dépend pas du droit de créer un site : un membre simple y a droit", () => {
    // L'étape est facultative et ne crée rien : la refuser à un membre invité
    // sur un site partagé le priverait de la seule source de données réelles.
    expect(canEnterGscStep(snapshot({ canCreateSite: false }))).toBe(true);
  });
});

describe("canEnterKeywordsStep", () => {
  const snapshot = (over: Partial<ActivationSnapshot> = {}) =>
    ({
      flags: none,
      dismissed: false,
      hasAccessibleSite: true,
      canCreateSite: true,
      ...over,
    }) satisfies ActivationSnapshot;

  it("ouvre l'étape dès qu'un site est accessible et le réservoir vide", () => {
    expect(canEnterKeywordsStep(snapshot())).toBe(true);
  });

  it("la referme dès qu'un mot-clé existe : sinon le rechargement reboucle", () => {
    expect(
      canEnterKeywordsStep(
        snapshot({ flags: { ...none, "add-keywords": true } }),
      ),
    ).toBe(false);
  });

  it("la refuse sans site accessible : l'import n'aurait pas de cible", () => {
    expect(canEnterKeywordsStep(snapshot({ hasAccessibleSite: false }))).toBe(
      false,
    );
  });

  it("la refuse sans snapshot plutôt que d'ouvrir un écran à l'aveugle", () => {
    expect(canEnterKeywordsStep(null)).toBe(false);
  });

  it("ne dépend pas du droit de créer un site : un membre simple y a droit", () => {
    // Un membre invité sur un site partagé doit pouvoir peupler ce site — c'est
    // une étape REQUISE, la lui refuser l'enfermerait dans une activation
    // impossible à terminer.
    expect(canEnterKeywordsStep(snapshot({ canCreateSite: false }))).toBe(true);
  });
});

describe("helpers", () => {
  it("requiredComplete est faux tant qu'une étape requise manque", () => {
    expect(requiredComplete(none)).toBe(false);
    expect(requiredComplete({ ...all, "target-country": false })).toBe(false);
  });

  it("requiredComplete ignore les étapes non requises", () => {
    expect(
      requiredComplete({
        ...all,
        "enrich-keywords": false,
        "connect-gsc": false,
        "invite-teammate": false,
      }),
    ).toBe(true);
  });

  it("doneCount compte toutes les étapes, requises ou non", () => {
    expect(doneCount(none)).toBe(0);
    expect(doneCount(all)).toBe(6);
  });

  it("firstIncompleteStep suit l'ordre d'affichage de la checklist", () => {
    expect(firstIncompleteStep(none)).toBe("create-site");
    expect(firstIncompleteStep({ ...none, "create-site": true })).toBe(
      "add-keywords",
    );
    expect(firstIncompleteStep(all)).toBeNull();
  });

  it("firstIncompleteStep ne suggère jamais une latérale tant qu'une requise manque", () => {
    // create-site faite : add-keywords (requise) passe devant connect-gsc
    // (latérale), qui la précède pourtant dans le wizard.
    expect(firstIncompleteStep({ ...none, "create-site": true })).toBe(
      "add-keywords",
    );
    // create-site + add-keywords faites : target-country (requise) passe devant
    // enrich-keywords (latérale), qui la précède pourtant dans le wizard.
    expect(
      firstIncompleteStep({
        ...none,
        "create-site": true,
        "add-keywords": true,
      }),
    ).toBe("target-country");
  });

  it("firstIncompleteStep bascule sur les latérales une fois les requises faites", () => {
    expect(
      firstIncompleteStep({
        ...none,
        "create-site": true,
        "add-keywords": true,
        "target-country": true,
      }),
    ).toBe("connect-gsc");
    expect(firstIncompleteStep({ ...all, "invite-teammate": false })).toBe(
      "invite-teammate",
    );
  });
});
