/**
 * Processor Validation Generator
 *
 * Menghasilkan blok kode JavaScript untuk validasi input di router layer
 * (trust boundary HTTP -> processor). Blok ini di-inject ke file router hasil
 * generate; developer tidak perlu menulis validasi manual di processor.
 *
 * SECURITY BOUNDARY (jangan dilemahkan saat refactor):
 *   1. Output kode harus valid sintaks JavaScript; semua input field name
 *      diakses via bracket notation ('input[\'xxx\']') agar aman untuk
 *      nama non-identifier.
 *   2. Literal string di pesan error di-escape (kutip tunggal menjadi \\')
 *      agar tidak break string JavaScript hasil generate.
 *   3. Nilai regex (UUID, format) ditulis sebagai literal tetap; tidak
 *      menerima input dinamis untuk mencegah regex injection.
 *   4. Fallback ketika payload absen: emit komentar no-op, bukan throw,
 *      demi backward compatibility payload v1.x.
 */

const SOURCES = ['body', 'params', 'headers'];

// Tipe yang didukung oleh validator (harus sinkron dengan validatePayloadV2).
const VALID_TYPES = new Set([
  'string', 'text', 'number', 'integer', 'boolean',
  'uuid', 'date', 'datetime', 'array', 'object'
]);

// Format shorthand -> regex. Whitelist tetap untuk cegah regex injection.
const FORMAT_REGEX = {
  'email': "/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/",
  'url': "/^https?:\\/\\/[^\\s]+$/",
  'phone-id': "/^(\\+62|62|0)8[1-9][0-9]{6,10}$/",
  'uuid': "/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i"
};

const FORMAT_MESSAGES = {
  'email': 'must be a valid email address',
  'url': 'must be a valid URL',
  'phone-id': 'must be a valid Indonesian phone number',
  'uuid': 'must be a valid UUID'
};

/**
 * Escape literal string agar aman dimasukkan ke dalam string literal
 * ber-kutip tunggal di kode JavaScript yang di-generate.
 * @param {string} value
 * @returns {string}
 */
function escapeSingleQuoted(value) {
  if (typeof value !== 'string') return '';
  return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
}

/**
 * Bangun chain if / else if dari array check. Tiap check match tunggal yang
 * pertama benar akan push error; chain memastikan satu error per field.
 *
 * @param {string} safeField - Nama field yang sudah di-escape.
 * @param {string} indent - Indentasi outer (mis. '    ' atau '      ').
 * @param {Array<{condition: string, message: string}>} checks
 * @returns {string}
 */
function buildIfElseChain(safeField, indent, checks) {
  return checks.map((check, idx) => {
    const keyword = idx === 0 ? 'if' : 'else if';
    const prefix = idx === 0 ? indent : ' ';
    return `${prefix}${keyword} (${check.condition}) {\n` +
      `${indent}  __validationErrors.push({ field: '${safeField}', message: '${escapeSingleQuoted(check.message)}' });\n` +
      `${indent}}`;
  }).join('');
}

/**
 * Generate blok kode validasi untuk satu field.
 * - Required: chain if/else if di indent 4-space (ran langsung di route body).
 * - Non-required: bungkus di `if (present) { if/else if ... }` agar field
 *   absen tidak dianggap error.
 *
 * @param {string} inputField - Nama field di object input.
 * @param {string} messagePrefix - Prefix pesan error (mis. 'Field "order_id"' atau 'Header X-App-Code').
 * @param {boolean} required
 * @param {Array<{condition: string, message: string}>} followUpChecks
 * @returns {string}
 */
function buildFieldCheckBlock(inputField, messagePrefix, required, followUpChecks) {
  const safeField = escapeSingleQuoted(inputField);

  const checks = [];
  if (required) {
    checks.push({
      condition: `input['${safeField}'] === undefined || input['${safeField}'] === null || input['${safeField}'] === ''`,
      message: `${messagePrefix} is required`
    });
  }
  for (const check of followUpChecks) {
    checks.push({ condition: check.condition, message: check.message });
  }

  if (checks.length === 0) return '';

  if (required) {
    return buildIfElseChain(safeField, '    ', checks);
  }

  // Non-required: bungkus di presence guard.
  const presenceGuard = `input['${safeField}'] !== undefined && input['${safeField}'] !== null && input['${safeField}'] !== ''`;
  const inner = buildIfElseChain(safeField, '      ', checks);
  return `    if (${presenceGuard}) {\n${inner}\n    }`;
}

/**
 * Bangun daftar follow-up check (setelah existence check lolos) berdasarkan
 * definisi field: type, format, enum, min/maxLength.
 *
 * @param {string} inputField
 * @param {string} messagePrefix - Prefix pesan error (mis. 'Field "order_id"' atau 'Header X-App-Code').
 * @param {Object} fieldDef
 * @returns {Array<{condition: string, message: string}>}
 */
function buildFollowUpChecks(inputField, messagePrefix, fieldDef) {
  const checks = [];
  const safeField = escapeSingleQuoted(inputField);
  const type = fieldDef && typeof fieldDef.type === 'string' ? fieldDef.type : null;

  // Type check.
  if (type && VALID_TYPES.has(type)) {
    switch (type) {
      case 'string':
      case 'text':
        // text divalidasi identik dengan string (unbounded string); pemetaan UI
        // (→ textarea) bukan urusan lapisan validasi ini.
        checks.push({
          condition: `typeof input['${safeField}'] !== 'string'`,
          message: `${messagePrefix} must be a string`
        });
        break;
      case 'number':
        checks.push({
          condition: `typeof input['${safeField}'] !== 'number' && isNaN(Number(input['${safeField}']))`,
          message: `${messagePrefix} must be a number`
        });
        break;
      case 'integer':
        checks.push({
          condition: `!Number.isInteger(Number(input['${safeField}']))`,
          message: `${messagePrefix} must be an integer`
        });
        break;
      case 'boolean':
        checks.push({
          condition: `typeof input['${safeField}'] !== 'boolean' && input['${safeField}'] !== 'true' && input['${safeField}'] !== 'false'`,
          message: `${messagePrefix} must be a boolean`
        });
        break;
      case 'uuid':
        checks.push({
          condition: `typeof input['${safeField}'] !== 'string'`,
          message: `${messagePrefix} must be a string`
        });
        checks.push({
          condition: `!${FORMAT_REGEX.uuid}.test(input['${safeField}'])`,
          message: `${messagePrefix} must be a valid UUID`
        });
        break;
      case 'array':
        checks.push({
          condition: `!Array.isArray(input['${safeField}'])`,
          message: `${messagePrefix} must be an array`
        });
        break;
      case 'object':
        checks.push({
          condition: `typeof input['${safeField}'] !== 'object' || Array.isArray(input['${safeField}'])`,
          message: `${messagePrefix} must be an object`
        });
        break;
      default:
        break;
    }
  }

  // Format check (overlap-safe: uuid sudah ditangani di type).
  if (fieldDef && typeof fieldDef.format === 'string' && FORMAT_REGEX[fieldDef.format] && fieldDef.format !== 'uuid') {
    checks.push({
      condition: `typeof input['${safeField}'] === 'string' && !${FORMAT_REGEX[fieldDef.format]}.test(input['${safeField}'])`,
      message: `${messagePrefix} ${FORMAT_MESSAGES[fieldDef.format]}`
    });
  }

  // Enum whitelist.
  if (fieldDef && Array.isArray(fieldDef.enum) && fieldDef.enum.length > 0) {
    const safeValues = fieldDef.enum
      .filter((v) => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')
      .map((v) => typeof v === 'string' ? `'${escapeSingleQuoted(v)}'` : String(v));
    if (safeValues.length > 0) {
      const listLiteral = `[${safeValues.join(', ')}]`;
      checks.push({
        condition: `!${listLiteral}.includes(input['${safeField}'])`,
        message: `${messagePrefix} is not one of the allowed values`
      });
    }
  }

  // minLength / maxLength untuk string.
  if (fieldDef && Number.isInteger(fieldDef.minLength) && fieldDef.minLength >= 0) {
    checks.push({
      condition: `typeof input['${safeField}'] === 'string' && input['${safeField}'].length < ${fieldDef.minLength}`,
      message: `${messagePrefix} must be at least ${fieldDef.minLength} characters`
    });
  }
  if (fieldDef && Number.isInteger(fieldDef.maxLength) && fieldDef.maxLength >= 0) {
    checks.push({
      condition: `typeof input['${safeField}'] === 'string' && input['${safeField}'].length > ${fieldDef.maxLength}`,
      message: `${messagePrefix} must be at most ${fieldDef.maxLength} characters`
    });
  }

  return checks;
}

/**
 * Generate blok kode validasi router untuk satu processor.
 *
 * Perilaku:
 *   - `proc.request.validate === false`          -> emit komentar opt-out.
 *   - `proc.request` absen                       -> emit komentar no-op.
 *   - Ada field definition                       -> emit array __validationErrors
 *     dengan chain cek per field, lalu HTTP 400 response jika ada error.
 *
 * @param {Object} proc - Processor definition.
 * @returns {string} Blok kode JavaScript (indent 4 spasi, tanpa trailing newline).
 */
function generateRouterValidationCode(proc) {
  if (proc && proc.request && proc.request.validate === false) {
    return '    // Router validation di-opt-out via "request.validate": false. Validasi adalah tanggung jawab processor.';
  }
  if (!proc || !proc.request) {
    return '    // Tidak ada validation rules di payload. Pastikan processor melakukan validasi.';
  }

  const fieldBlocks = [];
  for (const source of SOURCES) {
    const section = proc.request[source];
    if (!section || typeof section !== 'object') continue;

    for (const [fieldName, rawDef] of Object.entries(section)) {
      const fieldDef = rawDef && typeof rawDef === 'object' ? rawDef : {};
      const inputField = source === 'headers' && typeof fieldDef.mapTo === 'string' && fieldDef.mapTo.length > 0
        ? fieldDef.mapTo
        : fieldName;
      // Prefix pesan error: "Header X-App-Code" untuk headers, "Field \"xxx\"" untuk body/params.
      const messagePrefix = source === 'headers'
        ? `Header ${escapeSingleQuoted(fieldName)}`
        : `Field "${escapeSingleQuoted(inputField)}"`;

      const followUps = buildFollowUpChecks(inputField, messagePrefix, fieldDef);
      const required = fieldDef.required === true;

      if (!required && followUps.length === 0) continue;
      const block = buildFieldCheckBlock(inputField, messagePrefix, required, followUps);
      if (block) fieldBlocks.push(block);
    }
  }

  if (fieldBlocks.length === 0) {
    return '    // Tidak ada field yang perlu divalidasi di router. Pastikan processor melakukan validasi business rule.';
  }

  const header =
    '    // Auto-generated input validation (router trust boundary)\n' +
    '    // Opt-out via "request.validate": false di payload processor item.\n' +
    '    const __validationErrors = [];';

  const errorResponse =
    '    if (__validationErrors.length > 0) {\n' +
    '      return res.status(400).json({\n' +
    '        success: false,\n' +
    '        statusCode: 400,\n' +
    '        message: \'Validation failed\',\n' +
    '        errors: __validationErrors,\n' +
    '        timestamp: new Date().toISOString()\n' +
    '      });\n' +
    '    }';

  return [header, ...fieldBlocks, errorResponse].join('\n');
}

module.exports = {
  generateRouterValidationCode,
  buildFollowUpChecks,
  buildFieldCheckBlock,
  escapeSingleQuoted,
  VALID_TYPES,
  FORMAT_REGEX
};
