import type { RegistrarAlertaInput } from 'src/alertas-empresa/alertas-empresa.types';
import { claveAlertaEvento, TIPO_ALERTA_EVENTO_SIFEN } from './alerta-evento-sifen';
import type { TipoDocumento } from './middleware-sifen.service';

/**
 * Qué hacer con una cancelación (ECAN) que SIFEN rechazó.
 *
 * SIFEN a veces responde un error propio, sin relación con el documento
 * ("0100 Error Inesperado", "3590 Fallo al Guardar Eventos"): la cancelación no
 * se registra y la factura sigue vigente. Antes eso quedaba sólo en el detalle
 * del documento; nadie se enteraba y, pasado el plazo, ya no se podía cancelar
 * (DOBA, jun–ago 2026: 4 facturas que siguieron como deuda del cliente).
 * Ahora el sync la reintenta sola mientras dure el plazo y, si no lo logra,
 * deja una alerta que dice qué hacer.
 */

/** Plazo de SIFEN para cancelar: 48 h la factura, 168 h los demás documentos. */
export const PLAZO_CANCELACION_MS: Record<TipoDocumento, number> = {
  factura: 48 * 3600_000,
  nota_credito: 168 * 3600_000,
  nota_debito: 168 * 3600_000,
  nota_remision: 168 * 3600_000,
};

/** No se reintenta tan sobre el vencimiento: el envío y la respuesta tardan. */
export const MARGEN_VENCIMIENTO_MS = 15 * 60_000;
/** Reintentos automáticos por documento. */
export const MAX_REINTENTOS_AUTO = 6;
/** Espera mínima entre un envío y el reintento siguiente. */
export const ESPERA_ENTRE_REINTENTOS_MS = 15 * 60_000;

/** Errores de SIFEN que no dependen del documento: tiene sentido reintentar. */
export function esRechazoTransitorio(codRespuesta?: string | null, mensaje?: string | null): boolean {
  const cod = String(codRespuesta ?? '').trim();
  if (cod === '0100' || cod === '3590') return true;
  const msg = String(mensaje ?? '').trim().toLowerCase();
  return msg.startsWith('error inesperado') || msg.startsWith('fallo al guardar eventos');
}

export type DecisionCancelacion =
  | { accion: 'reintentar'; venceEl: Date }
  | { accion: 'esperar'; venceEl: Date }
  | { accion: 'alertar'; motivo: 'reintentos_agotados' | 'no_transitorio'; venceEl: Date }
  | { accion: 'alertar'; motivo: 'vencido'; venceEl: Date | null };

export function decidirCancelacionRechazada(params: {
  tipoDocumento: TipoDocumento;
  /** Cuándo SIFEN aprobó el documento (desde ahí corre el plazo). */
  aprobadoEl: Date | null;
  /** Último envío del evento. */
  ultimoEnvio: Date | null;
  intentosAuto: number;
  transitorio: boolean;
  ahora: Date;
}): DecisionCancelacion {
  const { tipoDocumento, aprobadoEl, ultimoEnvio, intentosAuto, transitorio, ahora } = params;
  // Sin fecha de aprobación no se puede saber si sigue en plazo: no se arriesga
  // un reintento que SIFEN rechazaría por extemporáneo.
  if (!aprobadoEl) return { accion: 'alertar', motivo: 'vencido', venceEl: null };

  const venceEl = new Date(aprobadoEl.getTime() + PLAZO_CANCELACION_MS[tipoDocumento]);
  if (ahora.getTime() >= venceEl.getTime() - MARGEN_VENCIMIENTO_MS) {
    return { accion: 'alertar', motivo: 'vencido', venceEl };
  }
  if (!transitorio) return { accion: 'alertar', motivo: 'no_transitorio', venceEl };
  if (intentosAuto >= MAX_REINTENTOS_AUTO) return { accion: 'alertar', motivo: 'reintentos_agotados', venceEl };

  const desdeUltimo = ultimoEnvio ? ahora.getTime() - ultimoEnvio.getTime() : Infinity;
  return desdeUltimo >= ESPERA_ENTRE_REINTENTOS_MS ? { accion: 'reintentar', venceEl } : { accion: 'esperar', venceEl };
}

/** Caso de la alerta (alerta.datos.caso). El panel ofrece "Reenviar" sólo si no venció. */
export const CASO_CANCELACION_RECHAZADA = 'cancelacion_rechazada';

const LABEL_DOCUMENTO: Record<TipoDocumento, string> = {
  factura: 'la factura',
  nota_credito: 'la nota de crédito',
  nota_remision: 'la nota de remisión',
  nota_debito: 'la nota de débito',
};

const TAB_DOCUMENTO: Record<TipoDocumento, string> = {
  factura: 'facturas',
  nota_credito: 'notas-credito',
  nota_remision: 'remisiones',
  nota_debito: 'notas-debito',
};

const fmtFechaHora = (d: Date) =>
  d.toLocaleString('es-PY', { timeZone: 'America/Asuncion', day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });

export function alertaCancelacionRechazada(params: {
  empresaId: string;
  tipoDocumento: TipoDocumento;
  doc: { id: string; dest: string | null; dpunexp: string | null; dnumdoc: string | null };
  decision: DecisionCancelacion;
  mensajeSifen: string | null;
  intentosAuto: number;
  sucursalId: string | null;
}): RegistrarAlertaInput {
  const { empresaId, tipoDocumento, doc, decision, mensajeSifen, intentosAuto, sucursalId } = params;
  const numero = `${doc.dest ?? '???'}-${doc.dpunexp ?? '???'}-${doc.dnumdoc ?? '???????'}`;
  const docLabel = LABEL_DOCUMENTO[tipoDocumento];
  const respuesta = mensajeSifen ? `SIFEN respondió: "${mensajeSifen}".` : 'SIFEN la rechazó.';
  const vence = decision.venceEl ? fmtFechaHora(decision.venceEl) : null;
  const vencido = decision.accion === 'alertar' && decision.motivo === 'vencido';

  let criticidad: 'alta' | 'media' = 'alta';
  let descripcion: string;
  let accion: string;
  if (decision.accion === 'reintentar' || decision.accion === 'esperar') {
    criticidad = 'media';
    descripcion = `${respuesta} Es un error de SIFEN, no del documento: el sistema la vuelve a enviar solo (${intentosAuto} de ${MAX_REINTENTOS_AUTO} reintentos hechos).`;
    accion = `No hace falta hacer nada por ahora. Si no se registra, reenviala antes del ${vence}.`;
  } else if (vencido) {
    descripcion = `${respuesta} ${docLabel.charAt(0).toUpperCase()}${docLabel.slice(1)} sigue vigente${vence ? ` y el plazo para cancelarla venció el ${vence}` : ''}.`;
    accion =
      tipoDocumento === 'factura'
        ? 'Ya no se puede cancelar: para dejarla sin efecto emití una nota de crédito por el total.'
        : 'Ya no se puede cancelar: dejala sin efecto con el documento que corresponda.';
  } else {
    descripcion = `${respuesta} ${docLabel.charAt(0).toUpperCase()}${docLabel.slice(1)} sigue vigente.${decision.accion === 'alertar' && decision.motivo === 'reintentos_agotados' ? ` Se reintentó ${intentosAuto} veces sin éxito.` : ''}`;
    accion = `Reenviá la cancelación antes del ${vence}. Pasado ese plazo sólo se puede anular con una nota de crédito.`;
  }

  return {
    empresaId,
    clave: claveAlertaEvento(tipoDocumento, doc.id),
    tipo: TIPO_ALERTA_EVENTO_SIFEN,
    criticidad,
    titulo: `La cancelación de ${docLabel} ${numero} no se registró en SIFEN`,
    descripcion,
    accionSugerida: accion,
    modulo: 'FISCAL',
    privilegioRequerido: 'FIS_SIF_SIFEN_VER',
    ruta: `/ventas?tab=${TAB_DOCUMENTO[tipoDocumento]}&buscar=${encodeURIComponent(numero)}`,
    sucursalId,
    entidadTipo: tipoDocumento,
    entidadId: doc.id,
    datos: {
      caso: CASO_CANCELACION_RECHAZADA,
      tipoDocumento,
      documentoId: doc.id,
      numero,
      evento: 'ECAN',
      vencido,
      vence_el: decision.venceEl ? decision.venceEl.toISOString() : null,
    },
    origen: 'sistema',
  };
}

/**
 * Motivo con el que se pidió originalmente el evento. Cuando SIFEN rechaza,
 * `mensaje_evento` se pisa con su respuesta ("Error Inesperado"), así que el
 * motivo se rescata de la auditoría del envío: `new_value.motivo` desde que se
 * guarda, o el "| Motivo: …" de la descripción en los envíos anteriores.
 */
export function motivoDeAuditoria(registros: Array<{ descripcion: string | null; new_value: unknown }>): string | null {
  for (const r of registros) {
    const nv = (r.new_value ?? {}) as Record<string, unknown>;
    if (typeof nv.motivo === 'string' && nv.motivo.trim()) return nv.motivo.trim();
    const m = /\| Motivo: (.+)$/.exec(r.descripcion ?? '');
    if (m && m[1].trim()) return m[1].trim();
  }
  return null;
}
