import { randomBytes, createCipheriv, createDecipheriv } from 'crypto';
import { existsSync, readFileSync, writeFileSync, chmodSync, mkdirSync } from 'fs';
import { dirname } from 'path';

const ALGO = 'aes-256-gcm';
const IV_LEN = 12;
const TAG_LEN = 16;

/** Read or initialize the master key (32 bytes) at `path`. File mode is forced to 0600. */
export function initMasterKey(path: string): Buffer {
  if (existsSync(path)) {
    const buf = readFileSync(path);
    if (buf.length !== 32) {
      throw new Error(`Master key at ${path} is not 32 bytes (got ${buf.length})`);
    }
    return buf;
  }
  mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
  const key = randomBytes(32);
  writeFileSync(path, key, { mode: 0o600 });
  chmodSync(path, 0o600);
  return key;
}

export function generateUserDek(): Buffer {
  return randomBytes(32);
}

function encrypt(key: Buffer, plaintext: Buffer): Buffer {
  const iv = randomBytes(IV_LEN);
  const cipher = createCipheriv(ALGO, key, iv);
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
  const tag = cipher.getAuthTag();
  return Buffer.concat([iv, ciphertext, tag]);
}

function decrypt(key: Buffer, blob: Buffer): Buffer {
  if (blob.length < IV_LEN + TAG_LEN) throw new Error('blob too short');
  const iv = blob.subarray(0, IV_LEN);
  const tag = blob.subarray(blob.length - TAG_LEN);
  const ciphertext = blob.subarray(IV_LEN, blob.length - TAG_LEN);
  const decipher = createDecipheriv(ALGO, key, iv);
  decipher.setAuthTag(tag);
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
}

export function encryptUserDek(masterKey: Buffer, dek: Buffer): Buffer {
  return encrypt(masterKey, dek);
}

export function decryptUserDek(masterKey: Buffer, enc: Buffer): Buffer {
  return decrypt(masterKey, enc);
}

export function encryptStateBlob(dek: Buffer, plaintext: string): Buffer {
  return encrypt(dek, Buffer.from(plaintext, 'utf-8'));
}

export function decryptStateBlob(dek: Buffer, blob: Buffer): string {
  return decrypt(dek, blob).toString('utf-8');
}
