/**
 * Clasifica el mensaje que dejó SIFEN (o el middleware) en el documento.
 *
 * El código de respuesta de SIFEN NO se guarda en el documento del ERP: solo
 * `mensaje_sifen`. Por eso la clasificación es por texto. Cada patrón tiene su
 * caso real en clasificar-mensaje-sifen.spec.ts.
 */
export type ClaseMensaje =
  | 'sin_mensaje'
  /** "Documento electrónico duplicado": suele estar aprobado en SIFEN y el ERP lo rescata solo. */
  | 'duplicado'
  /** La empresa no está sincronizada con e-FacturaHub: lo resuelve Novasis. */
  | 'empresa_no_en_hub'
  /** Certificado vencido o inválido: lo renueva el cliente. */
  | 'certificado'
  /** Falla del lado de SIFEN o de la red: no es un dato del cliente. */
  | 'falla_transitoria'
  /** Falla de la plataforma (reloj de firma, error interno del middleware). */
  | 'falla_plataforma'
  /**
   * "Se detectaron valores inválidos en algunos documentos" sin el detalle de
   * SIFEN (el JSON viene truncado): el cliente no tiene qué corregir.
   */
  | 'validacion_sin_detalle'
  /** Validación de datos del documento: lo corrige el cliente. */
  | 'dato_cliente'
  /** El documento no está rechazado ni con error: el mensaje solo informa ("Aprobado", "CDC encontrado"). */
  | 'informativo';

const PATRONES: Array<[ClaseMensaje, RegExp]> = [
  ['duplicado', /duplicad/i],
  // "login al middleware: Middleware SIFEN login error 400: Usuario no válido": el ERP no puede
  // entrar al middleware con el usuario de la empresa (alta o credenciales en e-FacturaHub).
  ['empresa_no_en_hub', /no se encontraron datos de la empresa|login al middleware|middleware sifen login error/i],
  ['certificado', /certificado[^.]{0,60}(vencid|expirad|revocad|no\s+v[aá]lid|inv[aá]lid)/i],
  [
    'falla_transitoria',
    /(?<!\d)0100(?!\d)|\bpki\b|time\s?out|tiempo de espera|no disponible|econn\w*|socket hang up|error inesperado|(?<!\d)50[234](?!\d)/i,
  ],
  // "XML malformado: [El elemento esperado es: iDenTarj en lugar de: iForProPa]" es el XML
  // armado en otro orden: falla de la plataforma. Un valor inválido ("El valor X del
  // elemento: dGtin es invalido") sí es un dato del cliente y no entra acá.
  ['falla_plataforma', /firma digital es adelantada|middleware sifen error 5\d\d|error interno|elemento esperado es/i],
];

/**
 * Diagnóstico de una alerta por el texto de su error, para que el agente explique la causa y no
 * solo repita el mensaje. null si no es un error conocido (la alerta ya trae qué hacer).
 */
export function diagnosticoDeAlerta(
  descripcion: string | null | undefined,
): { causa: string; lo_resuelve: 'novasis' | 'el_cliente' | 'el_sistema_solo'; requiere_novasis: boolean } | null {
  switch (clasificarMensajeSifen(descripcion)) {
    case 'empresa_no_en_hub':
      return {
        causa:
          'El sistema no puede conectarse al servicio de facturación electrónica con el usuario de la empresa (el alta o las credenciales de la empresa en ese servicio). No es un problema de los documentos ni algo que el cliente pueda corregir; reintentar o volver a emitir no lo arregla.',
        lo_resuelve: 'novasis',
        requiere_novasis: true,
      };
    case 'falla_plataforma':
      return {
        causa: 'Falla de la plataforma de facturación electrónica, no de los datos del documento.',
        lo_resuelve: 'novasis',
        requiere_novasis: true,
      };
    case 'falla_transitoria':
      return {
        causa: 'Falla momentánea de SIFEN o de la conexión. El sistema reintenta solo.',
        lo_resuelve: 'el_sistema_solo',
        requiere_novasis: false,
      };
    case 'certificado':
      return {
        causa: 'El certificado digital de la empresa está vencido o no es válido: hay que renovarlo.',
        lo_resuelve: 'el_cliente',
        requiere_novasis: false,
      };
    default:
      return null;
  }
}

export function clasificarMensajeSifen(mensaje: string | null | undefined): ClaseMensaje {
  const m = (mensaje ?? '').trim();
  if (!m) return 'sin_mensaje';
  for (const [clase, patron] of PATRONES) {
    if (patron.test(m)) return clase;
  }
  if (/se detectaron valores inv[aá]lidos/i.test(textoDelMensaje(m) ?? '')) return 'validacion_sin_detalle';
  return 'dato_cliente';
}

/** Mensajes de homologación vienen con "TEST - " adelante. */
export function esAmbienteTest(mensaje: string | null | undefined): boolean {
  return /^\s*TEST\s*-/i.test(mensaje ?? '');
}

/**
 * Texto presentable del mensaje: sin el prefijo "TEST - " y, si es un error del
 * middleware con JSON, el motivo de adentro ("Middleware SIFEN error 400: {...}").
 * Devuelve null si no hay nada útil para mostrar.
 */
export function textoDelMensaje(mensaje: string | null | undefined): string | null {
  let m = (mensaje ?? '').trim();
  if (!m) return null;
  m = m.replace(/^\s*TEST\s*-\s*/i, '');

  const json = m.indexOf('{');
  if (/^middleware sifen error/i.test(m) && json >= 0) {
    // Primero el detalle de SIFEN (dMsgRes), después los mensajes genéricos.
    // Si el JSON viene truncado (mensaje_sifen tiene largo limitado), se busca con regex.
    let cuerpo: unknown = null;
    try {
      cuerpo = JSON.parse(m.slice(json));
    } catch {
      cuerpo = null;
    }
    for (const clave of ['dMsgRes', 'mensaje', 'message']) {
      const valor = cuerpo ? buscarClave(cuerpo, clave) : m.match(new RegExp(`"${clave}"\\s*:\\s*"([^"]+)"`))?.[1];
      if (valor) return valor;
    }
  }
  return m;
}

/** Primer string no vacío bajo `clave`, en profundidad. */
function buscarClave(nodo: unknown, clave: string, profundidad = 0): string | null {
  if (profundidad > 8 || nodo == null || typeof nodo !== 'object') return null;
  const o = nodo as Record<string, unknown>;
  if (!Array.isArray(o) && typeof o[clave] === 'string' && o[clave]) return o[clave];
  for (const v of Object.values(o)) {
    const r = buscarClave(v, clave, profundidad + 1);
    if (r) return r;
  }
  return null;
}
