import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';

const DEFAULT_SECRET_PURPOSE = 'platform-settings:prime-api-key';

function additionalAuthenticatedData(purpose: string): Buffer {
  return Buffer.from(`pig:${purpose}:v1`);
}

export class SecretConfigurationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'SecretConfigurationError';
  }
}

function masterKey(value: string | undefined): Buffer {
  if (!value) {
    throw new SecretConfigurationError(
      'PIG_SETTINGS_ENCRYPTION_KEY is required before credentials can be stored.',
    );
  }
  const key = Buffer.from(value, 'base64');
  if (key.length !== 32 || key.toString('base64').replace(/=+$/, '') !== value.replace(/=+$/, '')) {
    throw new SecretConfigurationError(
      'PIG_SETTINGS_ENCRYPTION_KEY must be a base64-encoded 32-byte key.',
    );
  }
  return key;
}

export function encryptionReady(value: string | undefined): boolean {
  try {
    masterKey(value);
    return true;
  } catch {
    return false;
  }
}

export function encryptSecret(
  plaintext: string,
  keyValue: string | undefined,
  purpose = DEFAULT_SECRET_PURPOSE,
): string {
  const key = masterKey(keyValue);
  const iv = randomBytes(12);
  const cipher = createCipheriv('aes-256-gcm', key, iv);
  cipher.setAAD(additionalAuthenticatedData(purpose));
  const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  return ['v1', iv.toString('base64url'), tag.toString('base64url'), ciphertext.toString('base64url')].join('.');
}

export function decryptSecret(
  envelope: string,
  keyValue: string | undefined,
  purpose = DEFAULT_SECRET_PURPOSE,
): string {
  const [version, ivValue, tagValue, ciphertextValue, extra] = envelope.split('.');
  if (version !== 'v1' || !ivValue || !tagValue || !ciphertextValue || extra) {
    throw new SecretConfigurationError('Stored credential has an unsupported format.');
  }
  try {
    const decipher = createDecipheriv('aes-256-gcm', masterKey(keyValue), Buffer.from(ivValue, 'base64url'));
    decipher.setAAD(additionalAuthenticatedData(purpose));
    decipher.setAuthTag(Buffer.from(tagValue, 'base64url'));
    return Buffer.concat([
      decipher.update(Buffer.from(ciphertextValue, 'base64url')),
      decipher.final(),
    ]).toString('utf8');
  } catch (error) {
    if (error instanceof SecretConfigurationError) throw error;
    throw new SecretConfigurationError('Stored credential could not be decrypted.');
  }
}
