// profile-dek.ts — Space-as-Principal P2 DEK selection for browser session
// profiles (file-master domain). A space-owned profile encrypts its state under
// the SPACE DEK so it survives member turnover; a personal/legacy profile keeps
// the per-user DEK. Decryption tries the space DEK first and falls back to the
// owner's user DEK, so a profile written under the OLD owner DEK before it became
// space-owned still reads (re-encryption is a separate migration; reads stay
// correct without it).
import {
  initMasterKey,
  generateUserDek,
  encryptUserDek,
  decryptUserDek,
  decryptStateBlob,
} from './sessions.js';

export interface ProfileDekRepo {
  getUserDek(userId: string): Buffer | null;
  setUserDek(userId: string, encryptedDek: Buffer): void;
  getSpaceDek(spaceId: string): Buffer | null;
  setSpaceDek(spaceId: string, encryptedDek: Buffer): void;
}

export interface ProfileDekDeps {
  masterKeyPath: string;
  sessRepo: ProfileDekRepo;
}

/** Ensure + return the per-user DEK (32 bytes), creating + persisting lazily. */
function ensureUserDek(deps: ProfileDekDeps, master: Buffer, userId: string): Buffer {
  const enc = deps.sessRepo.getUserDek(userId);
  if (enc) return decryptUserDek(master, enc);
  const dek = generateUserDek();
  deps.sessRepo.setUserDek(userId, encryptUserDek(master, dek));
  return dek;
}

/** Ensure + return the per-space DEK (32 bytes), creating + persisting lazily. */
function ensureSpaceDek(deps: ProfileDekDeps, master: Buffer, spaceId: string): Buffer {
  const enc = deps.sessRepo.getSpaceDek(spaceId);
  if (enc) return decryptUserDek(master, enc);
  const dek = generateUserDek();
  deps.sessRepo.setSpaceDek(spaceId, encryptUserDek(master, dek));
  return dek;
}

/**
 * The DEK to ENCRYPT a profile's state with: the space DEK when the profile is
 * space-owned, else the owner's user DEK. Caller MUST `fill(0)` when done.
 */
export function ensureProfileEncryptDek(
  deps: ProfileDekDeps,
  ownerId: string,
  spaceId: string | null,
): Buffer {
  const master = initMasterKey(deps.masterKeyPath);
  return spaceId ? ensureSpaceDek(deps, master, spaceId) : ensureUserDek(deps, master, ownerId);
}

/**
 * Decrypt a profile's state blob, trying the space DEK first (when space-owned)
 * and falling back to the owner's user DEK for a pre-migration blob. Throws if
 * neither key decrypts the blob.
 */
export function decryptProfileState(
  deps: ProfileDekDeps,
  profile: { ownerId: string; spaceId: string | null; encryptedStateBlob: Buffer | null },
): string {
  if (profile.encryptedStateBlob == null) {
    throw new Error('profile-dek: profile has no saved state blob');
  }
  const master = initMasterKey(deps.masterKeyPath);
  if (profile.spaceId) {
    const encSpace = deps.sessRepo.getSpaceDek(profile.spaceId);
    if (encSpace) {
      const dek = decryptUserDek(master, encSpace);
      try {
        return decryptStateBlob(dek, profile.encryptedStateBlob);
      } catch {
        // Legacy blob written under the owner DEK — fall through.
      } finally {
        dek.fill(0);
      }
    }
  }
  const encUser = deps.sessRepo.getUserDek(profile.ownerId);
  if (!encUser) throw new Error('profile-dek: no DEK available for profile');
  const dek = decryptUserDek(master, encUser);
  try {
    return decryptStateBlob(dek, profile.encryptedStateBlob);
  } finally {
    dek.fill(0);
  }
}
