/**
 * @bytelyst/field-encrypt — Environment variable key provider
 *
 * For dev/staging — uses a hex-encoded symmetric key from an env var.
 * Matches the existing MFA pattern (AUTH_TOTP_ENCRYPTION_KEY).
 *
 * Wrapping uses AES-256-GCM with the env key as MEK.
 */

import { createCipheriv, createDecipheriv, randomBytes, createHash } from 'node:crypto';
import type { KeyProvider } from './types.js';

const ALGORITHM = 'aes-256-gcm';
const IV_BYTES = 12;

export class EnvKeyProvider implements KeyProvider {
  private readonly mek: Buffer;
  private readonly version: string;

  /**
   * @param keyHex - Hex-encoded 32-byte key (64 hex chars).
   *                 If shorter, it will be SHA-256 hashed to derive a 32-byte key.
   */
  constructor(keyHex: string) {
    if (!keyHex || keyHex.length === 0) {
      throw new Error('EnvKeyProvider: encryption key must not be empty');
    }

    if (keyHex.length === 64) {
      this.mek = Buffer.from(keyHex, 'hex');
    } else {
      // Hash to 32 bytes — same approach as existing MFA encryption
      this.mek = createHash('sha256').update(keyHex).digest();
    }

    this.version = 'env-v1';
  }

  async wrapKey(dek: Buffer): Promise<{ wrappedKey: string; mekVersion: string }> {
    const iv = randomBytes(IV_BYTES);
    const cipher = createCipheriv(ALGORITHM, this.mek, iv);
    let encrypted = cipher.update(dek);
    encrypted = Buffer.concat([encrypted, cipher.final()]);
    const tag = cipher.getAuthTag();

    const wrapped = Buffer.concat([iv, tag, encrypted]);
    return { wrappedKey: wrapped.toString('hex'), mekVersion: this.version };
  }

  async unwrapKey(wrappedKeyHex: string, _mekVersion: string): Promise<Buffer> {
    const wrapped = Buffer.from(wrappedKeyHex, 'hex');
    const iv = wrapped.subarray(0, IV_BYTES);
    const tag = wrapped.subarray(IV_BYTES, IV_BYTES + 16);
    const ciphertext = wrapped.subarray(IV_BYTES + 16);

    const decipher = createDecipheriv(ALGORITHM, this.mek, iv);
    decipher.setAuthTag(tag);
    let decrypted = decipher.update(ciphertext);
    decrypted = Buffer.concat([decrypted, decipher.final()]);
    return decrypted;
  }
}
