/**
 * @bytelyst/field-encrypt — Envelope encryption
 *
 * DEK lifecycle: generate → wrap with MEK → store.
 * On use: load wrapped DEK → unwrap with MEK → cache → use for AES-GCM.
 */

import type { KeyProvider, DekStore, WrappedDek } from './types.js';
import { generateAesKey } from './aes-gcm.js';
import { DekCache } from './key-cache.js';

/**
 * Build a deterministic DEK ID from userId + context.
 * Format: `dek_{userId}_{context}`
 */
export function buildDekId(userId: string, context: string): string {
  return `dek_${userId}_${context}`;
}

/**
 * Get or create a DEK for the given scope.
 *
 * 1. Check cache → return if found
 * 2. Check DEK store → unwrap + cache if found
 * 3. Generate new DEK → wrap → store → cache → return
 */
export async function getOrCreateDek(
  dekId: string,
  keyProvider: KeyProvider,
  dekStore: DekStore,
  cache: DekCache
): Promise<Buffer> {
  // 1. Cache hit
  const cached = cache.get(dekId);
  if (cached) {
    cache.recordHit();
    return cached;
  }
  cache.recordMiss();

  // 2. DEK store hit — unwrap + cache
  const stored = await dekStore.get(dekId);
  if (stored) {
    const dek = await keyProvider.unwrapKey(stored.wrappedKey, stored.mekVersion);
    cache.set(dekId, dek);
    return dek;
  }

  // 3. Generate new DEK → wrap → store → cache
  const dek = generateAesKey();
  const { wrappedKey, mekVersion } = await keyProvider.wrapKey(dek);

  const wrappedDek: WrappedDek = {
    dekId,
    wrappedKey,
    mekVersion,
    createdAt: new Date().toISOString(),
  };
  await dekStore.put(wrappedDek);

  cache.set(dekId, dek);
  return dek;
}

/**
 * Re-wrap all DEKs after MEK rotation.
 *
 * Reads each wrapped DEK, unwraps with old MEK, wraps with new MEK, stores updated.
 */
export async function rewrapAllDeks(
  oldKeyProvider: KeyProvider,
  newKeyProvider: KeyProvider,
  dekStore: DekStore,
  cache: DekCache,
  onProgress?: (completed: number, total: number) => void
): Promise<number> {
  const dekIds = await dekStore.listIds();
  let completed = 0;

  for (const dekId of dekIds) {
    const stored = await dekStore.get(dekId);
    if (!stored) continue;

    // Unwrap with old MEK
    const rawDek = await oldKeyProvider.unwrapKey(stored.wrappedKey, stored.mekVersion);

    // Wrap with new MEK
    const { wrappedKey, mekVersion } = await newKeyProvider.wrapKey(rawDek);

    // Store updated wrapped DEK
    const updated: WrappedDek = {
      dekId,
      wrappedKey,
      mekVersion,
      createdAt: stored.createdAt,
    };
    await dekStore.put(updated);

    // Invalidate cache entry so it gets re-unwrapped with new MEK next time
    cache.invalidate(dekId);

    completed++;
    onProgress?.(completed, dekIds.length);
  }

  return completed;
}
