import { createCipheriv, createDecipheriv, randomBytes, createHash, hkdfSync } from 'crypto';

const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 12;
const AUTH_TAG_LENGTH = 16;

/** Legacy prefix (SHA-256 derived key) — read-only, never written for new data */
const LEGACY_PREFIX = 'enc:';
/** V2 prefix (HKDF derived key) — used for all new encryptions */
const V2_PREFIX = 'enc2:';

const HKDF_SALT = 'changemaker-lite-encryption-v2';
const HKDF_INFO = 'aes-256-gcm-data-encryption';

/** Derive a 32-byte key using SHA-256 (legacy, for decrypting old data) */
function deriveLegacyKey(secret: string): Buffer {
  return createHash('sha256').update(secret).digest();
}

/** Derive a 32-byte key using HKDF-SHA256 (RFC 5869) */
function deriveKey(secret: string): Buffer {
  return Buffer.from(
    hkdfSync('sha256', secret, HKDF_SALT, HKDF_INFO, 32)
  );
}

let _key: Buffer | null = null;
let _legacyKey: Buffer | null = null;

/** Initialize (or re-initialize) the encryption key. Call once at startup. */
export function initEncryption(secret: string): void {
  _key = deriveKey(secret);
  _legacyKey = deriveLegacyKey(secret);
}

function getKey(): Buffer {
  if (!_key) {
    throw new Error('Encryption not initialized — call initEncryption() first');
  }
  return _key;
}

function getLegacyKey(): Buffer {
  if (!_legacyKey) {
    throw new Error('Encryption not initialized — call initEncryption() first');
  }
  return _legacyKey;
}

/**
 * Encrypt a plaintext string.
 * Returns format: `enc2:<iv>:<authTag>:<ciphertext>` (all base64).
 */
export function encrypt(plaintext: string): string {
  if (!plaintext) return plaintext;

  const key = getKey();
  const iv = randomBytes(IV_LENGTH);
  const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
  const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
  const authTag = cipher.getAuthTag();

  return `${V2_PREFIX}${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted.toString('base64')}`;
}

/**
 * Decrypt a value produced by encrypt().
 * Supports both v2 (HKDF) and legacy (SHA-256) key derivation.
 * If the value doesn't have a recognized prefix, returns it as-is (backward compat for plaintext).
 */
export function decrypt(value: string): string {
  if (!value) return value;

  let key: Buffer;
  let payload: string;

  if (value.startsWith(V2_PREFIX)) {
    key = getKey();
    payload = value.slice(V2_PREFIX.length);
  } else if (value.startsWith(LEGACY_PREFIX)) {
    key = getLegacyKey();
    payload = value.slice(LEGACY_PREFIX.length);
  } else {
    return value; // Plaintext fallback
  }

  const parts = payload.split(':');
  if (parts.length !== 3) return value;

  const iv = Buffer.from(parts[0], 'base64');
  const authTag = Buffer.from(parts[1], 'base64');
  const ciphertext = Buffer.from(parts[2], 'base64');

  const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
  decipher.setAuthTag(authTag);
  const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);

  return decrypted.toString('utf8');
}

/** Check whether a value is already encrypted */
export function isEncrypted(value: string): boolean {
  return !!value && (value.startsWith(V2_PREFIX) || value.startsWith(LEGACY_PREFIX));
}
