// src/lib/gsc/site-token.ts — LE point de résolution du jeton GSC d'un site.
// Remplace les trois anciens chemins de résolution (jeton de l'owner de l'org,
// jeton du worker, jeton de la session courante), tous supprimés : un seul
// chemin, donc plus de divergence possible entre l'UI et le worker.
import "server-only";
import { auth } from "@/lib/auth";
import {
  deriveSiteGscState,
  type SiteGscFailure,
  type SiteStateDeps,
} from "./site-state";

export type SiteGscTokenResult =
  | {
      ok: true;
      accessToken: string;
      property: string;
      authorityUserId: string;
      authorityAccountId: string;
    }
  | { ok: false; reason: SiteGscFailure };

export type SiteTokenDeps = SiteStateDeps & {
  /**
   * @internal Réservé aux tests — la mise en garde de
   * `canAuthorityAccessSite` vaut ici mot pour mot.
   */
  getAccessToken?: (args: {
    body: { providerId: string; userId: string; accountId: string };
  }) => Promise<{ accessToken?: string } | null>;
};

/** INVARIANT : pas de `headers` dans l'appel. Better Auth fait gagner la
 *  session sur body.userId dès que des headers sont présents
 *  (`node_modules/better-auth/dist/api/routes/account.mjs:236-244`) — on
 *  résoudrait le jeton de l'APPELANT au lieu de celui de l'autorité, en
 *  silence, avec les données d'un autre compte Google.
 *
 *  Ce que les tests verrouillent, exactement : le test d'invariant capture les
 *  arguments à la frontière `resolveSiteGscToken → deps.getAccessToken`, il ne
 *  traverse donc PAS ce wrapper. C'est un second test, qui lit le source de ce
 *  fichier et refuse toute mention de `headers`, qui couvre la ligne
 *  ci-dessous. Ne « corrige » pas un debug en ajoutant `headers: await
 *  headers()` ici : ce serait la panne, pas le remède. */
const defaultGetAccessToken: NonNullable<SiteTokenDeps["getAccessToken"]> = (
  args,
) => auth.api.getAccessToken(args);

/**
 * Site → propriété + autorité → revalidation de l'accès → compte Google épinglé
 * + scope → jeton frais. Better Auth rafraîchit et déchiffre
 * (`encryptOAuthTokens` est actif) — ne jamais lire `account.access_token`
 * directement.
 *
 * ⚠️ Frontière d'autorisation pour l'AUTORITÉ SEULEMENT. Sur le chemin
 * headerless, Better Auth ne vérifie plus rien pour nous, par conception :
 * cette fonction porte donc la revalidation de l'accès de l'autorité au site.
 * Elle ne vérifie EN REVANCHE jamais que l'APPELANT a le droit de toucher ce
 * site — le worker n'a pas de session, ce serait impossible. Toute server
 * action / route qui l'appelle DOIT gater son appelant séparément, via
 * `currentUserCanAccessSite(siteId)`, sinon un `siteId` deviné suffit à lire
 * les données GSC d'une autre organisation.
 */
export async function resolveSiteGscToken(
  siteId: string,
  deps?: SiteTokenDeps,
): Promise<SiteGscTokenResult> {
  const derived = await deriveSiteGscState(siteId, deps);
  if (derived.state !== "ok") {
    return { ok: false, reason: derived.state };
  }
  // Union discriminée : les trois champs sont non-null PAR LE TYPE ici.
  const { property, authorityUserId, authorityAccountId } = derived;
  const getAccessToken = deps?.getAccessToken ?? defaultGetAccessToken;
  try {
    // `accountId` est obligatoire : sans lui, account.mjs:270 refait sa propre
    // sélection arbitraire parmi les comptes Google de l'utilisateur, et le
    // jeton peut venir d'un compte sans droit sur la propriété — 403 Google
    // opaque au lieu de la raison typée que ce module promet.
    const res = await getAccessToken({
      body: {
        providerId: "google",
        userId: authorityUserId,
        accountId: authorityAccountId,
      },
    });
    if (!res?.accessToken) {
      console.error("[gsc] getAccessToken sans accessToken", {
        siteId,
        authorityUserId,
        authorityAccountId,
      });
      return { ok: false, reason: "token-refused" };
    }
    // Trace d'audit : jamais le jeton, juste qui/quoi/quand. Le compte Google
    // en fait partie — le rafraîchissement OAuth ÉCRIT dans ce compte, une
    // trace muette là-dessus ne remplirait pas son office. Sert aussi à
    // repérer un sync déclenché sous impersonation.
    console.info("[gsc] jeton résolu", {
      siteId,
      authorityUserId,
      authorityAccountId,
      at: new Date().toISOString(),
    });
    return {
      ok: true,
      accessToken: res.accessToken,
      property,
      authorityUserId,
      authorityAccountId,
    };
  } catch (err) {
    // Cause typique : refresh token Google révoqué, ou BETTER_AUTH_SECRET
    // changé (déchiffrement impossible). Le message suffit — jamais le jeton.
    console.error("[gsc] getAccessToken en échec", {
      siteId,
      authorityUserId,
      authorityAccountId,
      error: err instanceof Error ? err.message : String(err),
    });
    return { ok: false, reason: "token-refused" };
  }
}
