// src/components/onboarding/steps/gsc-step.test.tsx — l'étape Search Console
// du wizard, testée par sa logique PURE (`./gsc-step-logic`).
//
// Le dépôt n'a pas de harness de rendu React (@testing-library absent), et le
// script `bun run test` tourne sous `--conditions=react-server` : importer un
// composant client ferait crasher la suite. Le motif du dépôt s'applique donc
// ici (cf. `enrich-status-logic.test.ts`, `first-target.ts`) — la décision
// « qu'est-ce que l'écran propose » et « que se passe-t-il au clic » vit dans
// un module sans JSX, et c'est lui qu'on met sous test. Le composant n'est
// alors plus qu'un rendu de ces décisions.
import { describe, expect, mock, test } from "bun:test";
import type {
  GscSiteRow,
  GscStatus,
  OwnedGscProperty,
  SetSiteGscResult,
} from "@/lib/gsc/actions";
import { createGscStepHandlers, resolveGscStepView } from "./gsc-step-logic";

const SITE_ID = "site-1";

function property(siteUrl: string): OwnedGscProperty {
  return { siteUrl, permissionLevel: "siteOwner", accountId: "acc-1" };
}

function siteRow(gscProperty: string | null): GscSiteRow {
  return {
    id: SITE_ID,
    name: "Mon site",
    url: "https://exemple.fr",
    gscProperty,
    authorityUserId: null,
    authorityName: null,
    isMine: false,
    state: gscProperty ? "no-authority" : "no-property",
  };
}

function status(over: Partial<GscStatus> = {}): GscStatus {
  return {
    myGoogle: "linked",
    properties: [],
    propertiesError: false,
    sites: [siteRow(null)],
    ...over,
  };
}

describe("resolveGscStepView", () => {
  test("aucun compte Google → proposer la liaison", () => {
    expect(resolveGscStepView(status({ myGoogle: "none" }), SITE_ID)).toEqual({
      kind: "link",
    });
  });

  test("compte lié sans le scope → proposer la reconnexion", () => {
    expect(
      resolveGscStepView(status({ myGoogle: "missing-scope" }), SITE_ID),
    ).toEqual({ kind: "reconnect" });
  });

  test("compte lié avec des propriétés → sélecteur, valeur du site courant", () => {
    const view = resolveGscStepView(
      status({
        properties: [property("sc-domain:exemple.fr")],
        sites: [siteRow("sc-domain:exemple.fr")],
      }),
      SITE_ID,
    );
    expect(view).toEqual({
      kind: "select",
      properties: [property("sc-domain:exemple.fr")],
      current: "sc-domain:exemple.fr",
      incomplete: false,
    });
  });

  test("site courant introuvable dans les lignes → sélecteur sans valeur", () => {
    const view = resolveGscStepView(
      status({ properties: [property("sc-domain:exemple.fr")], sites: [] }),
      SITE_ID,
    );
    expect(view).toMatchObject({ kind: "select", current: null });
  });

  test("échec de vérification AVEC des propriétés → sélecteur marqué incomplet", () => {
    // La liste n'est plus une mesure mais un plancher : on la propose quand
    // même (elle est utilisable) en signalant qu'elle peut être trouée.
    const view = resolveGscStepView(
      status({
        properties: [property("sc-domain:exemple.fr")],
        propertiesError: true,
      }),
      SITE_ID,
    );
    expect(view).toMatchObject({ kind: "select", incomplete: true });
  });

  test("échec de vérification SANS propriété → « indisponible », jamais « aucune »", () => {
    // Le piège que ce test verrouille : afficher « tu n'as aucune propriété »
    // quand Google n'a tout simplement pas répondu.
    expect(
      resolveGscStepView(status({ propertiesError: true }), SITE_ID),
    ).toEqual({ kind: "unavailable" });
  });

  test("compte lié, zéro propriété, aucune erreur → état « site trop récent »", () => {
    expect(resolveGscStepView(status(), SITE_ID)).toEqual({ kind: "empty" });
  });

  test("aucune vue ne peut retenir l'utilisateur : la sortie existe partout", () => {
    // L'étape est facultative (`connect-gsc` est `required: false`) : quelle
    // que soit la branche, l'écran doit offrir une sortie. Le test énumère
    // toutes les combinaisons atteignables et vérifie qu'aucune ne produit une
    // vue inconnue du composant — donc aucune branche sans sortie rendue.
    const kinds = new Set<string>();
    for (const myGoogle of ["none", "missing-scope", "linked"] as const) {
      for (const propertiesError of [false, true]) {
        for (const properties of [[], [property("sc-domain:exemple.fr")]]) {
          kinds.add(
            resolveGscStepView(
              status({ myGoogle, propertiesError, properties }),
              SITE_ID,
            ).kind,
          );
        }
      }
    }
    expect([...kinds].sort()).toEqual([
      "empty",
      "link",
      "reconnect",
      "select",
      "unavailable",
    ]);
  });
});

describe("createGscStepHandlers", () => {
  const OK: () => Promise<SetSiteGscResult> = async () => ({ ok: true });

  function deps(result: () => Promise<SetSiteGscResult> = OK) {
    return {
      setProperty: mock(async (_siteId: string, _property: string | null) =>
        result(),
      ),
      onMapped: mock(() => {}),
      onSkip: mock(() => {}),
      onError: mock((_message: string) => {}),
      fallbackError: "échec réseau",
    };
  }

  test("passer avance le wizard SANS écrire de propriété", () => {
    const d = deps();
    createGscStepHandlers(SITE_ID, d).skip();
    expect(d.onSkip).toHaveBeenCalledTimes(1);
    expect(d.setProperty).not.toHaveBeenCalled();
    expect(d.onError).not.toHaveBeenCalled();
  });

  test("choisir une propriété l'écrit sur le site COURANT", async () => {
    const d = deps();
    await createGscStepHandlers(SITE_ID, d).select("sc-domain:exemple.fr");
    expect(d.setProperty).toHaveBeenCalledWith(SITE_ID, "sc-domain:exemple.fr");
    expect(d.onMapped).toHaveBeenCalledTimes(1);
    expect(d.onError).not.toHaveBeenCalled();
  });

  test("refus du serveur → message affiché TEL QUEL, étape non franchie", () => {
    // Le serveur distingue « propriété pas à toi » de « Google n'a pas
    // répondu » : le remplacer par une phrase générique renverrait
    // l'utilisateur démapper sur une panne transitoire.
    const d = deps(async () => ({
      ok: false,
      error: "Propriété GSC non vérifiée ou inconnue.",
    }));
    return createGscStepHandlers(SITE_ID, d)
      .select("sc-domain:autre.fr")
      .then(() => {
        expect(d.onError).toHaveBeenCalledWith(
          "Propriété GSC non vérifiée ou inconnue.",
        );
        expect(d.onMapped).not.toHaveBeenCalled();
      });
  });

  test("action qui lève → message de repli, pas de crash", async () => {
    const d = deps(async () => {
      throw new Error("boom");
    });
    await createGscStepHandlers(SITE_ID, d).select("sc-domain:exemple.fr");
    expect(d.onError).toHaveBeenCalledWith("échec réseau");
    expect(d.onMapped).not.toHaveBeenCalled();
  });
});
