// Chiffrement au repos des secrets applicatifs (server_secret HMAC, clé privée VAPID) stockés dans
// la table `settings`. node:sqlite (DatabaseSync) ne supporte pas sqlcipher → on chiffre au niveau
// applicatif les VALEURS sensibles, en AES-256-GCM (authentifié), avant insertion.
//
// Gestion de clé (par ordre de priorité) :
//   1. ARBORETUM_SECRET_KEY (variable d'env) → vraie protection : la clé ne vit pas sur le disque,
//      donc une fuite de la base seule (backup, WAL) ne révèle pas les secrets ;
//   2. sinon, fichier clé `dataDir/secret.key` (0o600), généré au 1er démarrage. Protection partielle :
//      couvre la fuite de la base seule, PAS celle du dossier de données complet (clé + base ensemble).
// Compromis assumé et documenté (docs/ENTERPRISE_DEPLOYMENT.md) : pour une protection forte, fournir
// ARBORETUM_SECRET_KEY et sauvegarder cette clé séparément des backups de la base.
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
import { chmodSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';

const PREFIX = 'v1:'; // versionne le format ; absence de préfixe = valeur en clair (legacy → migration douce)
const SCRYPT_SALT = 'arboretum-secret-box-v1'; // sel fixe : l'entropie vient de la passphrase fournie

export class SecretBox {
  constructor(private readonly key: Buffer) {}

  /** Chiffre en `v1:<iv>:<tag>:<ciphertext>` (hex). */
  encrypt(plaintext: string): string {
    const iv = randomBytes(12);
    const cipher = createCipheriv('aes-256-gcm', this.key, iv);
    const ct = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
    const tag = cipher.getAuthTag();
    return `${PREFIX}${iv.toString('hex')}:${tag.toString('hex')}:${ct.toString('hex')}`;
  }

  /** Déchiffre une valeur `v1:` ; une valeur sans préfixe est retournée telle quelle (clair legacy). */
  decrypt(stored: string): string {
    if (!this.isEncrypted(stored)) return stored;
    const [, ivHex, tagHex, ctHex] = stored.split(':');
    if (!ivHex || !tagHex || !ctHex) throw new Error('secret-box: format chiffré invalide');
    const decipher = createDecipheriv('aes-256-gcm', this.key, Buffer.from(ivHex, 'hex'));
    decipher.setAuthTag(Buffer.from(tagHex, 'hex'));
    return Buffer.concat([decipher.update(Buffer.from(ctHex, 'hex')), decipher.final()]).toString('utf8');
  }

  isEncrypted(stored: string): boolean {
    return stored.startsWith(PREFIX);
  }
}

/**
 * Construit le SecretBox de production : clé dérivée d'ARBORETUM_SECRET_KEY si fournie, sinon
 * d'un fichier clé `dataDir/secret.key` (0o600) généré au besoin.
 */
export function loadSecretBox(dataDir: string): SecretBox {
  const passphrase = process.env.ARBORETUM_SECRET_KEY;
  if (passphrase && passphrase.length > 0) {
    return new SecretBox(scryptSync(passphrase, SCRYPT_SALT, 32));
  }
  const keyPath = join(dataDir, 'secret.key');
  let keyHex: string;
  if (existsSync(keyPath)) {
    keyHex = readFileSync(keyPath, 'utf8').trim();
  } else {
    keyHex = randomBytes(32).toString('hex');
    writeFileSync(keyPath, keyHex + '\n', { mode: 0o600 });
  }
  try {
    chmodSync(keyPath, 0o600);
  } catch {
    /* FS sans permissions POSIX : ignoré */
  }
  return new SecretBox(Buffer.from(keyHex, 'hex'));
}
