/**
 * Enterprise secret obfuscation — light symmetric crypto for tokens
 * that need to live in an enterprise repo's wizard template (which
 * ships through git) without sitting in plaintext for casual scrapers.
 *
 * This is OBFUSCATION, not real security. The key is derived purely
 * from the enterprise name (`fortinet`, `acme`, …), so anyone with
 * the algorithm and the company name can decrypt — but a clone of
 * the repo on its own gives nothing useful.
 *
 * Threat model:
 *   ✓ Github web-scraper / search bot grabs the repo. They see
 *     `ent:…` blobs; without knowing the algo + tenant name, the
 *     token doesn't fall out.
 *   ✓ Casual leak (someone posts the template content on Slack).
 *     Plaintext token doesn't appear in the paste.
 *   ✗ Determined attacker who knows this is Fortinet's repo and runs
 *     the decryption with key='fortinet'. They get the token.
 *
 * For real secrets that must survive a determined attacker, prompt
 * the user instead (`_prompts` block) and let Forge encrypt locally
 * with the per-instance `.encrypt-key`.
 *
 * Format: `ent:<iv-base64>.<tag-base64>.<ciphertext-base64>`
 * Algorithm: AES-256-GCM, key = SHA-256(enterpriseName.toLowerCase().trim())
 */

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

const PREFIX = 'ent:';

function deriveKey(enterpriseName: string): Buffer {
  return createHash('sha256').update(enterpriseName.toLowerCase().trim()).digest();
}

export function isEnterpriseSecret(value: unknown): value is string {
  return typeof value === 'string' && value.startsWith(PREFIX);
}

export function encryptForEnterprise(plaintext: string, enterpriseName: string): string {
  if (!plaintext) return '';
  const key = deriveKey(enterpriseName);
  const iv = randomBytes(12);
  const cipher = createCipheriv('aes-256-gcm', key, iv);
  const enc = Buffer.concat([cipher.update(plaintext, 'utf-8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  return `${PREFIX}${iv.toString('base64')}.${tag.toString('base64')}.${enc.toString('base64')}`;
}

export function decryptForEnterprise(blob: string, enterpriseName: string): string {
  if (!isEnterpriseSecret(blob)) return blob;
  try {
    const payload = blob.slice(PREFIX.length);
    const [ivB64, tagB64, encB64] = payload.split('.');
    if (!ivB64 || !tagB64 || !encB64) return '';
    const key = deriveKey(enterpriseName);
    const iv = Buffer.from(ivB64, 'base64');
    const tag = Buffer.from(tagB64, 'base64');
    const data = Buffer.from(encB64, 'base64');
    const decipher = createDecipheriv('aes-256-gcm', key, iv);
    decipher.setAuthTag(tag);
    return Buffer.concat([decipher.update(data), decipher.final()]).toString('utf-8');
  } catch {
    // Wrong key → auth tag mismatch → throws. Treat as "couldn't decrypt"
    // and return empty so callers don't silently use a garbage value.
    return '';
  }
}

/**
 * Walk an arbitrary JSON value, decrypting any string that looks like
 * an `ent:…` blob. Non-strings + non-blobs pass through unchanged.
 * Useful for the wizard-import path — apply this to the resolved
 * template before merging into settings.
 */
export function decryptDeep(value: unknown, enterpriseName: string): unknown {
  if (isEnterpriseSecret(value)) return decryptForEnterprise(value, enterpriseName);
  if (Array.isArray(value)) return value.map((v) => decryptDeep(v, enterpriseName));
  if (value && typeof value === 'object') {
    const out: Record<string, unknown> = {};
    for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
      out[k] = decryptDeep(v, enterpriseName);
    }
    return out;
  }
  return value;
}
