/**
 * Sensitive Field Masker
 *
 * Utility untuk:
 *   1. Masking field sensitif di runtime (digunakan di test dan tool internal).
 *   2. Generate kode JavaScript untuk masker helper yang di-inject ke router file
 *      hasil generate (digunakan oleh create-processor generator).
 *
 * SECURITY BOUNDARY (jangan dilemahkan saat refactor):
 *   1. Regex pattern anchored (^...$) untuk hindari false positive via substring match.
 *   2. Extra keywords dari payload di-sanitasi strict (identifier-only) untuk cegah
 *      regex injection attack melalui nama field yang berisi meta-character regex.
 *   3. Masker helper code di-generate sebagai literal string; tidak boleh
 *      meng-embed user input apapun tanpa sanitasi.
 */

// Default pattern untuk deteksi field sensitif.
// Pattern anchored (^...$) untuk exact match, case-insensitive.
const DEFAULT_SENSITIVE_PATTERN = /^(password|passwd|pwd|secret|token|api_?key|auth(orization)?|credit_?card|card_?number|cvv|ssn|pin|private_?key|refresh_?token|access_?token|otp|mfa)$/i;

// Regex whitelist untuk extra keyword: identifier JavaScript (a-z, A-Z, 0-9, _).
// Tidak allow meta-character regex seperti '.', '*', '(', '[', dll.
const SAFE_KEYWORD_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;

/**
 * Sanitasi daftar extra keyword agar aman disisipkan ke regex literal.
 * Filter keluarkan semua keyword yang mengandung meta-character regex.
 *
 * @param {string[]} keywords - Daftar keyword tambahan dari payload.
 * @returns {string[]} Daftar keyword lowercase yang aman.
 */
function sanitizeExtraKeywords(keywords) {
  if (!Array.isArray(keywords)) return [];
  const seen = new Set();
  const sanitized = [];
  for (const kw of keywords) {
    if (typeof kw !== 'string') continue;
    if (!SAFE_KEYWORD_PATTERN.test(kw)) continue;
    const lower = kw.toLowerCase();
    if (seen.has(lower)) continue;
    seen.add(lower);
    sanitized.push(lower);
  }
  return sanitized;
}

/**
 * Generate blok kode JavaScript yang mendefinisikan helper __maskSensitive
 * dan konstanta SENSITIVE_FIELD_PATTERNS. Output ini di-inject ke file router
 * hasil generate sehingga console.log di router otomatis melakukan masking.
 *
 * @param {Object} [options]
 * @param {string[]} [options.extraKeywords] - Daftar nama field tambahan yang
 *   harus ikut di-mask (selain default pattern). Hanya identifier yang aman
 *   yang akan diterima; sisanya di-filter.
 * @returns {string} Blok kode JavaScript siap-inject.
 */
function buildMaskerHelperCode(options = {}) {
  const extra = sanitizeExtraKeywords(options.extraKeywords || []);
  const extraGroup = extra.length > 0 ? '|' + extra.join('|') : '';

  return `const SENSITIVE_FIELD_PATTERNS = /^(password|passwd|pwd|secret|token|api_?key|auth(orization)?|credit_?card|card_?number|cvv|ssn|pin|private_?key|refresh_?token|access_?token|otp|mfa${extraGroup})$/i;

function __maskSensitive(obj) {
  if (!obj || typeof obj !== 'object') return obj;
  const masked = Array.isArray(obj) ? [...obj] : { ...obj };
  for (const key of Object.keys(masked)) {
    if (SENSITIVE_FIELD_PATTERNS.test(key)) {
      masked[key] = '***MASKED***';
    } else if (masked[key] && typeof masked[key] === 'object') {
      masked[key] = __maskSensitive(masked[key]);
    }
  }
  return masked;
}`;
}

/**
 * Runtime masking untuk object (rekursif) di lingkungan generator/test.
 * Fungsi ini tidak di-inject ke output; gunakan buildMaskerHelperCode untuk itu.
 *
 * @param {*} obj - Object atau nilai apapun yang akan di-mask.
 * @param {RegExp} [pattern] - Pattern custom (default: DEFAULT_SENSITIVE_PATTERN).
 * @returns {*} Salinan yang field sensitifnya sudah di-mask.
 */
function maskSensitive(obj, pattern = DEFAULT_SENSITIVE_PATTERN) {
  if (!obj || typeof obj !== 'object') return obj;
  const masked = Array.isArray(obj) ? [...obj] : { ...obj };
  for (const key of Object.keys(masked)) {
    if (pattern.test(key)) {
      masked[key] = '***MASKED***';
    } else if (masked[key] && typeof masked[key] === 'object') {
      masked[key] = maskSensitive(masked[key], pattern);
    }
  }
  return masked;
}

/**
 * Tentukan apakah sebuah field sensitif berdasarkan definisi payload
 * (flag explicit sensitive:true) atau pattern default pada nama field.
 *
 * @param {string} fieldName - Nama field.
 * @param {Object} [fieldDef] - Definisi field dari payload (opsional).
 * @returns {boolean}
 */
function isFieldSensitive(fieldName, fieldDef) {
  if (fieldDef && fieldDef.sensitive === true) return true;
  if (typeof fieldName !== 'string') return false;
  return DEFAULT_SENSITIVE_PATTERN.test(fieldName);
}

/**
 * Kumpulkan daftar nama field yang di-flag sensitive:true di seluruh processor
 * di payload. Digunakan untuk meng-extend pattern masker saat generate router.
 *
 * @param {Array} processors - Array processor dari payload.
 * @returns {string[]} Daftar nama field unik yang di-flag sensitive.
 */
function collectExplicitSensitiveKeywords(processors) {
  if (!Array.isArray(processors)) return [];
  const seen = new Set();
  for (const proc of processors) {
    if (!proc || !proc.request) continue;
    for (const source of ['body', 'params', 'headers']) {
      const section = proc.request[source];
      if (!section || typeof section !== 'object') continue;
      for (const [fieldName, fieldDef] of Object.entries(section)) {
        if (!fieldDef || typeof fieldDef !== 'object') continue;
        if (fieldDef.sensitive !== true) continue;
        // Untuk header dengan mapTo, gunakan mapTo (karena itu nama di input).
        const key = source === 'headers' && fieldDef.mapTo ? fieldDef.mapTo : fieldName;
        if (typeof key !== 'string') continue;
        seen.add(key);
      }
    }
  }
  return Array.from(seen);
}

module.exports = {
  DEFAULT_SENSITIVE_PATTERN,
  SAFE_KEYWORD_PATTERN,
  buildMaskerHelperCode,
  maskSensitive,
  isFieldSensitive,
  sanitizeExtraKeywords,
  collectExplicitSensitiveKeywords
};
