// src/lib/connectors/crypto.ts — chiffrement au repos des secrets de
// connecteurs (clés API BYO : SE-Ranking aujourd'hui, Mangools/… demain).
// AES-256-GCM, clé dérivée de BETTER_AUTH_SECRET par HKDF — même racine de
// secret que l'encryptOAuthTokens de Better Auth (src/lib/auth.ts:117), mais
// implémentation à nous car Better Auth ne couvre que les tokens OAuth des
// comptes liés, pas les colonnes applicatives.
//
// Format de blob : `v1:<iv>:<tag>:<ct>` (base64url). Le préfixe versionné
// permet le fallback legacy (valeurs historiques stockées en clair) et une
// éventuelle rotation d'algorithme.
import {
  createCipheriv,
  createDecipheriv,
  hkdfSync,
  randomBytes,
} from "node:crypto";

const VERSION = "v1";
const ALG = "aes-256-gcm";
const IV_BYTES = 12; // taille nominale GCM
const HKDF_SALT = "seo-insight";
const HKDF_INFO = "org-connectors";

function deriveKey(secret: string): Buffer {
  if (!secret) throw new Error("connector_secret_missing");
  return Buffer.from(hkdfSync("sha256", secret, HKDF_SALT, HKDF_INFO, 32));
}

function envSecret(): string {
  return process.env.BETTER_AUTH_SECRET ?? "";
}

/** Chiffre un secret (clé API) pour stockage en base. IV aléatoire : deux
 *  appels sur le même clair produisent des blobs différents. */
export function encryptSecret(plain: string, secret = envSecret()): string {
  const key = deriveKey(secret);
  const iv = randomBytes(IV_BYTES);
  const cipher = createCipheriv(ALG, key, iv);
  const ct = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
  const tag = cipher.getAuthTag();
  return [
    VERSION,
    iv.toString("base64url"),
    tag.toString("base64url"),
    ct.toString("base64url"),
  ].join(":");
}

/** Déchiffre un blob produit par encryptSecret. Throw
 *  `connector_secret_invalid` sur format inconnu, tag altéré ou mauvais
 *  secret — jamais de retour silencieusement faux. */
export function decryptSecret(blob: string, secret = envSecret()): string {
  const parts = blob.split(":");
  if (parts.length !== 4 || parts[0] !== VERSION) {
    throw new Error("connector_secret_invalid");
  }
  const [, ivB64, tagB64, ctB64] = parts;
  try {
    const key = deriveKey(secret);
    const decipher = createDecipheriv(
      ALG,
      key,
      Buffer.from(ivB64, "base64url"),
    );
    decipher.setAuthTag(Buffer.from(tagB64, "base64url"));
    const pt = Buffer.concat([
      decipher.update(Buffer.from(ctB64, "base64url")),
      decipher.final(),
    ]);
    return pt.toString("utf8");
  } catch {
    throw new Error("connector_secret_invalid");
  }
}

/** Vrai si la valeur est un blob chiffré par nous (préfixe versionné). */
export function isEncryptedSecret(value: string): boolean {
  return value.startsWith(`${VERSION}:`);
}

/** Lecture tolérante pour les colonnes historiquement stockées en clair
 *  (organization.serankingApiKey d'avant ce fix) : blob v1 → déchiffre,
 *  valeur legacy → renvoyée telle quelle. À la prochaine sauvegarde (ou
 *  rotation de clé), la valeur repasse par encryptSecret et le legacy
 *  disparaît naturellement. */
export function decryptSecretOrLegacy(
  value: string,
  secret = envSecret(),
): string {
  return isEncryptedSecret(value) ? decryptSecret(value, secret) : value;
}
