import { TipoDocumento } from '../tipos-documento';
import { clasificarEnvioCorreo, EstadoCorreo } from './clasificar-envio-correo';
import { clasificarMensajeSifen, ClaseMensaje, esAmbienteTest, textoDelMensaje } from './clasificar-mensaje-sifen';

/**
 * Diagnóstico determinístico de un documento electrónico (plan §6.2).
 * El modelo explica el veredicto; no lo decide.
 */

export type Situacion =
  | 'APROBADO'
  | 'APROBADO_SIN_SIFEN'
  | 'CANCELADO'
  | 'INUTILIZADO'
  /** Pendiente en el ERP y nunca enviado: con envío en lote, lo manda el cliente. */
  | 'SIN_ENVIAR'
  | 'PENDIENTE_ENVIO'
  | 'EN_PROCESO'
  | 'TRABADO'
  | 'RECHAZO_DATO_CLIENTE'
  | 'RECHAZO_CERTIFICADO'
  | 'POSIBLE_DUPLICADO_APROBADO'
  | 'FALLA_SIFEN_TRANSITORIA'
  | 'FALLA_PLATAFORMA'
  | 'EMPRESA_NO_EN_HUB'
  | 'DESCONOCIDO';

export type ResultadoSugerido = 'RESUELTO' | 'RESUELTO_AUTOSERVICIO' | 'NO_RESUELTO';

export type MotivoNoResuelto =
  | 'trabado_sifen'
  | 'falla_sifen_persistente'
  | 'sync_empresa_hub'
  | 'no_encontrado'
  | 'sin_permiso'
  | 'consulta_sin_respuesta_en_guias'
  | 'posible_error_sistema'
  | 'cliente_disconforme'
  | 'fuera_de_alcance';

/** Lo que el cliente puede hacer por su cuenta. */
export type AccionCliente =
  | 'esperar'
  | 'enviar_a_sifen'
  | 'corregir_y_reenviar'
  | 'reenviar'
  | 'renovar_certificado'
  | 'revisar_alerta_evento'
  | 'cargar_email_receptor'
  | 'revisar_config_correo';

export type SituacionEvento = 'APROBADO' | 'EN_PROCESO' | 'RECHAZADO' | 'TRABADO';

export interface DocumentoParaDiagnostico {
  tipo: TipoDocumento;
  /** Estado del documento en el ERP (Pendiente, Aprobado, Anulado…), no el de SIFEN. */
  estado: string | null;
  nro_lote: string | null;
  estado_sifen: string | null;
  mensaje_sifen: string | null;
  tipo_aprobacion: string | null;
  fecha_envio_sifen: Date | null;
  created_at: Date | null;
  updated_at: Date | null;
  evento_aplicado: string | null;
  estado_evento: string | null;
  mensaje_evento: string | null;
  fecha_evento: Date | null;
  estado_envio_email: string | null;
}

export interface ContextoDiagnostico {
  ahora: Date;
  horasParaTrabado: number;
  /** empresas.sifen_activo; null si no se sabe. */
  sifenActivo: boolean | null;
}

export interface Veredicto {
  situacion: Situacion;
  resultado: ResultadoSugerido;
  motivo?: MotivoNoResuelto;
  accionCliente?: AccionCliente;
  /** Nunca sugerir reemitir: el documento ya existe o puede estar aprobado en SIFEN. */
  noReemitir: boolean;
  /** Mensaje de SIFEN presentable (sin "TEST - ", con el motivo de adentro del JSON). */
  mensaje: string | null;
  claseMensaje: ClaseMensaje;
  ambienteTest: boolean;
  horasEsperando: number | null;
  evento: { tipo: string; situacion: SituacionEvento; mensaje: string | null; accionCliente?: AccionCliente } | null;
  correo: { estado: EstadoCorreo; accionCliente?: AccionCliente };
}

const EN_CURSO = new Set(['enviando', 'enviado', 'pendiente', 'documento firmado']);

function horasDesde(fecha: Date | null, ahora: Date): number | null {
  if (!fecha) return null;
  return Math.max(0, (ahora.getTime() - new Date(fecha).getTime()) / 3_600_000);
}

function esperaOTrabado(
  horas: number | null,
  ctx: ContextoDiagnostico,
  situacionEnCurso: Situacion,
): Pick<Veredicto, 'situacion' | 'resultado' | 'motivo' | 'accionCliente' | 'noReemitir'> {
  // Sin fecha de referencia no se puede saber cuánto lleva: se informa como en curso.
  if (horas === null || horas < ctx.horasParaTrabado) {
    return { situacion: situacionEnCurso, resultado: 'RESUELTO', accionCliente: 'esperar', noReemitir: true };
  }
  return { situacion: 'TRABADO', resultado: 'NO_RESUELTO', motivo: 'trabado_sifen', noReemitir: true };
}

export function diagnosticarEvento(doc: DocumentoParaDiagnostico, ctx: ContextoDiagnostico): Veredicto['evento'] {
  if (!doc.evento_aplicado) return null;
  const estado = (doc.estado_evento ?? '').trim().toLowerCase();
  const mensaje = doc.mensaje_evento?.trim() || null;

  // "CDC ya se encuentra con el mismo evento solicitado": el evento ya estaba aplicado.
  if (estado === 'aprobado' || /mismo evento solicitado/i.test(mensaje ?? '')) {
    return { tipo: doc.evento_aplicado, situacion: 'APROBADO', mensaje };
  }
  if (estado === 'rechazado') {
    // El ERP reintenta solo dentro del plazo y deja una alerta con "Reenviar" / "Liberar".
    return { tipo: doc.evento_aplicado, situacion: 'RECHAZADO', mensaje, accionCliente: 'revisar_alerta_evento' };
  }
  const horas = horasDesde(doc.fecha_evento, ctx.ahora);
  if (horas !== null && horas >= ctx.horasParaTrabado) {
    return { tipo: doc.evento_aplicado, situacion: 'TRABADO', mensaje, accionCliente: 'revisar_alerta_evento' };
  }
  return { tipo: doc.evento_aplicado, situacion: 'EN_PROCESO', mensaje, accionCliente: 'esperar' };
}

export function diagnosticarCorreo(valor: string | null): Veredicto['correo'] {
  const estado = clasificarEnvioCorreo(valor);
  const accion: Partial<Record<EstadoCorreo, AccionCliente>> = {
    sin_destinatario: 'cargar_email_receptor',
    credenciales_smtp: 'revisar_config_correo',
    pendiente: 'esperar',
  };
  return accion[estado] ? { estado, accionCliente: accion[estado] } : { estado };
}

export function diagnosticarDocumento(doc: DocumentoParaDiagnostico, ctx: ContextoDiagnostico): Veredicto {
  const estado = (doc.estado_sifen ?? '').trim().toLowerCase();
  const claseMensaje: ClaseMensaje =
    estado === 'rechazado' || estado === 'error' ? clasificarMensajeSifen(doc.mensaje_sifen) : 'informativo';
  const base = {
    mensaje: textoDelMensaje(doc.mensaje_sifen),
    claseMensaje,
    ambienteTest: esAmbienteTest(doc.mensaje_sifen),
  };
  const horasEnvio = horasDesde(doc.fecha_envio_sifen ?? doc.updated_at ?? doc.created_at, ctx.ahora);
  const comun = {
    ...base,
    horasEsperando: null as number | null,
    evento: diagnosticarEvento(doc, ctx),
    correo: diagnosticarCorreo(doc.estado_envio_email),
  };
  const sinSifen =
    (doc.tipo_aprobacion ?? '').toUpperCase() === 'MANUAL' || /aprobado localmente/i.test(doc.mensaje_sifen ?? '');

  if (estado === 'aprobado') {
    return {
      ...comun,
      situacion: sinSifen ? 'APROBADO_SIN_SIFEN' : 'APROBADO',
      resultado: 'RESUELTO',
      noReemitir: true,
    };
  }

  if (estado === 'cancelado') {
    return { ...comun, situacion: 'CANCELADO', resultado: 'RESUELTO', noReemitir: true };
  }

  if (estado === 'inutilizado') {
    // La numeración quedó sin validez. El mensaje es el del rechazo original.
    return { ...comun, situacion: 'INUTILIZADO', resultado: 'RESUELTO', noReemitir: false };
  }

  if (!estado) {
    if (ctx.sifenActivo === false || sinSifen) {
      return { ...comun, situacion: 'APROBADO_SIN_SIFEN', resultado: 'RESUELTO', noReemitir: true };
    }
    // Con envío en lote (envio_lote_middleware, el default por sucursal) la factura queda
    // Pendiente hasta que el cliente la envía desde la pantalla. No es un documento trabado.
    if ((doc.estado ?? '').trim().toLowerCase() === 'pendiente' && !doc.nro_lote) {
      return {
        ...comun,
        situacion: 'SIN_ENVIAR',
        resultado: 'RESUELTO_AUTOSERVICIO',
        accionCliente: 'enviar_a_sifen',
        noReemitir: true,
      };
    }
    const horas = horasDesde(doc.created_at, ctx.ahora);
    return { ...comun, horasEsperando: horas, ...esperaOTrabado(horas, ctx, 'PENDIENTE_ENVIO') };
  }

  if (EN_CURSO.has(estado)) {
    return { ...comun, horasEsperando: horasEnvio, ...esperaOTrabado(horasEnvio, ctx, 'EN_PROCESO') };
  }

  if (estado === 'rechazado' || estado === 'error') {
    switch (claseMensaje) {
      case 'duplicado':
        // Suele estar aprobado en SIFEN; la sync del ERP lo rescata por CDC.
        return {
          ...comun,
          horasEsperando: horasEnvio,
          ...esperaOTrabado(horasEnvio, ctx, 'POSIBLE_DUPLICADO_APROBADO'),
        };
      case 'empresa_no_en_hub':
        return {
          ...comun,
          situacion: 'EMPRESA_NO_EN_HUB',
          resultado: 'NO_RESUELTO',
          motivo: 'sync_empresa_hub',
          noReemitir: true,
        };
      case 'certificado':
        return {
          ...comun,
          situacion: 'RECHAZO_CERTIFICADO',
          resultado: 'RESUELTO_AUTOSERVICIO',
          accionCliente: 'renovar_certificado',
          noReemitir: false,
        };
      case 'falla_transitoria':
        if (estado === 'error') {
          // Los errores de envío los reintenta la cola del ERP: se espera, y si persiste, a Novasis.
          const v = esperaOTrabado(horasEnvio, ctx, 'FALLA_SIFEN_TRANSITORIA');
          return v.situacion === 'TRABADO'
            ? {
                ...comun,
                horasEsperando: horasEnvio,
                ...v,
                situacion: 'FALLA_SIFEN_TRANSITORIA',
                motivo: 'falla_sifen_persistente',
              }
            : { ...comun, horasEsperando: horasEnvio, ...v };
        }
        // Rechazo por falla de SIFEN: no es un dato del cliente; se puede volver a enviar tal cual.
        return {
          ...comun,
          situacion: 'FALLA_SIFEN_TRANSITORIA',
          resultado: 'RESUELTO_AUTOSERVICIO',
          accionCliente: 'reenviar',
          noReemitir: true,
        };
      case 'validacion_sin_detalle':
      case 'falla_plataforma':
        return {
          ...comun,
          situacion: 'FALLA_PLATAFORMA',
          resultado: 'NO_RESUELTO',
          motivo: 'posible_error_sistema',
          noReemitir: true,
        };
      case 'dato_cliente': {
        // Un error del middleware sin motivo legible no le dice al cliente qué corregir.
        const motivoLegible = base.mensaje && !/^middleware sifen error/i.test(base.mensaje);
        if (estado === 'error' && !motivoLegible) {
          return {
            ...comun,
            situacion: 'DESCONOCIDO',
            resultado: 'NO_RESUELTO',
            motivo: 'posible_error_sistema',
            noReemitir: true,
          };
        }
        return {
          ...comun,
          situacion: 'RECHAZO_DATO_CLIENTE',
          resultado: 'RESUELTO_AUTOSERVICIO',
          accionCliente: 'corregir_y_reenviar',
          noReemitir: false,
        };
      }
      case 'sin_mensaje':
      case 'informativo':
        return {
          ...comun,
          situacion: 'DESCONOCIDO',
          resultado: 'NO_RESUELTO',
          motivo: 'posible_error_sistema',
          noReemitir: true,
        };
    }
  }

  return {
    ...comun,
    situacion: 'DESCONOCIDO',
    resultado: 'NO_RESUELTO',
    motivo: 'posible_error_sistema',
    noReemitir: true,
  };
}
