/*
 * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
 * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
 */
package com.crov.prase.controller.prase;

import com.arrebol.taxiservicios.controller.connection.ConnectionManager;
import com.arrebol.taxiservicios.controller.util.HibernateUtil;
import com.arrebol.taxiservicios.model.catalog.Location;
import com.arrebol.taxiservicios.model.core.User;
import com.arrebol.taxiservicios.model.enums.EstatusPagoMultiple;
import com.arrebol.taxiservicios.model.enums.EstatusSolicitud;
import com.arrebol.taxiservicios.model.enums.GenericEnumType;
import com.arrebol.taxiservicios.model.enums.MetodoPago;
import com.arrebol.taxiservicios.model.enums.PolizaEstatus;
import com.arrebol.taxiservicios.model.enums.TipoAbono;
import com.crov.prase.model.prase.DetellePagoPoliza;
import com.crov.prase.model.prase.HistorialAbonoMultiple;
import com.crov.prase.model.prase.PagosPoliza;
import com.crov.prase.model.prase.Poliza;
import com.crov.prase.model.prase.SolicitudDevolucionDetellePagoPoliza;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;

/**
 *
 * @author Crov
 */
public class PagosPolizaController extends ConnectionManager implements Serializable {

   private static final long serialVersionUID = -8772793021819350069L;
   final Logger logger = LogManager.getLogger(getClass());

   public List<PagosPoliza> fillPagosPolizaByPoliza(Poliza poliza) {
      logger.info("fillPagosPolizaByPoliza");
      List<PagosPoliza> resultList = null;
      Transaction transaction = null;
      try {
         logger.info("Searching pagos for poliza..");
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         CriteriaBuilder builder = session.getCriteriaBuilder();

         CriteriaQuery<PagosPoliza> criteria = builder.createQuery(PagosPoliza.class);
         Root<PagosPoliza> root = criteria.from(PagosPoliza.class);

         Predicate criterio1 = builder.equal(root.get("poliza"), poliza);
         Predicate criterio2 = builder.equal(root.get("estatusActivo"), GenericEnumType.ENABLED);
         criteria.where(builder.and(criterio1, criterio2));
         criteria.orderBy(builder.asc(root.get("fechaAPagar")));

         resultList = session.createQuery(criteria).getResultList();

         logger.info("Total of pagos found: " + (null == resultList ? -1 : resultList.size()));
         transaction.commit();
      } catch (HibernateException e) {
         logger.error("Pagos cannot be loaded ");
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method fillPagosPolizaByPoliza ", e);
         rollback(transaction);
      }
      return resultList;
   }

   public List<PagosPoliza> fillPagosPedientesPolizaByPoliza(Poliza poliza) {
      logger.info("fillPagosPolizaByPoliza");
      List<PagosPoliza> resultList = null;
      Transaction transaction = null;
      try {
         logger.info("Searching pagos for poliza..");
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         CriteriaBuilder builder = session.getCriteriaBuilder();

         CriteriaQuery<PagosPoliza> criteria = builder.createQuery(PagosPoliza.class);
         Root<PagosPoliza> root = criteria.from(PagosPoliza.class);

         Predicate criterio1 = builder.equal(root.get("poliza"), poliza);
         Predicate criterio2 = builder.equal(root.get("estatusActivo"), GenericEnumType.ENABLED);
         Predicate criterio3 = builder.equal(root.get("pagoEstatus"), GenericEnumType.DISABLED);
         Predicate criterio4 = builder.equal(root.get("pagoEstatus"), GenericEnumType.INCOMPLETE);
         criteria.where(builder.and(criterio1, criterio2), builder.or(criterio3, criterio4));
         criteria.orderBy(builder.asc(root.get("fechaAPagar")));

         resultList = session.createQuery(criteria).getResultList();

         logger.info("Total of pagos found: " + (null == resultList ? -1 : resultList.size()));
         transaction.commit();
      } catch (HibernateException e) {
         logger.error("Pagos cannot be loaded ");
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method fillPagosPolizaByPoliza ", e);
         rollback(transaction);
      }
      return resultList;
   }

   public List<PagosPoliza> fillPagosPolizaFechaAPagarByLocationFechaInicioAndFechaFin(Location location, Date startDate, Date endDate) {
      logger.info("fillPagosPolizaByPoliza");
      List<PagosPoliza> resultList = null;
      Transaction transaction = null;
      try {
         logger.info("Searching pagos for poliza..");
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         CriteriaBuilder builder = session.getCriteriaBuilder();

         CriteriaQuery<PagosPoliza> criteria = builder.createQuery(PagosPoliza.class);
         Root<PagosPoliza> root = criteria.from(PagosPoliza.class);

         LocalDate localHoy = new Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
         LocalDate localStartDate = startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
         LocalDate localEndDate = endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

         Predicate criterio1 = builder.equal(root.get("poliza").get("location"), location);
         Predicate criterio2 = builder.equal(root.get("estatusActivo"), GenericEnumType.ENABLED);
         Predicate criterio3 = builder.equal(root.get("pagoEstatus"), GenericEnumType.DISABLED);
         Predicate criterio4 = builder.equal(root.get("poliza").get("active"), true);
         Predicate criterio5 = builder.greaterThanOrEqualTo(root.get("poliza").get("endDate").as(LocalDate.class), localHoy);
         Predicate criterio6 = builder.notEqual(root.get("poliza").get("estatus"), PolizaEstatus.CANCELADO);
         Predicate startDatePredicate = builder.greaterThanOrEqualTo(root.get("fechaAPagar").as(LocalDate.class), localStartDate);
         Predicate endDatePredicate = builder.lessThanOrEqualTo(root.get("fechaAPagar").as(LocalDate.class), localEndDate);
         criteria.where(builder.and(criterio1, criterio2, criterio3, criterio4, criterio5, criterio6, startDatePredicate, endDatePredicate));
         criteria.orderBy(builder.asc(root.get("fechaAPagar")));

         resultList = session.createQuery(criteria).getResultList();

         logger.info("Total of pagos found: " + (null == resultList ? -1 : resultList.size()));
         transaction.commit();
      } catch (HibernateException e) {
         logger.error("Pagos cannot be loaded ");
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method fillPagosPolizaByPoliza ", e);
         rollback(transaction);
      }
      return resultList;
   }

   public DetellePagoPoliza fillFoliosByPagos(String abreviacionFolio) {
      DetellePagoPoliza result = null;
      Transaction transaction = null;
      try {
         logger.info("Search last generated folio..");

         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();

         String query = "SELECT tap FROM DetellePagoPoliza tap "
                 + "WHERE tap.folio LIKE '" + abreviacionFolio + "%' "
                 + "ORDER BY "
                 + "SUBSTRING_INDEX(tap.folio, '-', 1) DESC, "
                 + "SUBSTRING_INDEX(SUBSTRING_INDEX(tap.folio, '-', -2), '-', -6) DESC ";

         result = (DetellePagoPoliza) session.createQuery(query).setMaxResults(1).uniqueResult();
         transaction.commit();
      } catch (HibernateException e) {
         logger.error("Search for the last generated folio " + e);
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method fillFoliosByPagos() " + e);
         rollback(transaction);
      }
      return result;
   }

   public List<DetellePagoPoliza> fillDetellePagoPolizaPagadosByPoliza(Poliza poliza) {
      logger.info("fillDetellePagoPolizaByPoliza");
      List<DetellePagoPoliza> resultList = null;
      Transaction transaction = null;
      try {
         logger.info("Searching pagos for poliza..");
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         CriteriaBuilder builder = session.getCriteriaBuilder();

         CriteriaQuery<DetellePagoPoliza> criteria = builder.createQuery(DetellePagoPoliza.class);
         Root<DetellePagoPoliza> root = criteria.from(DetellePagoPoliza.class);

         Predicate criterio1 = builder.equal(root.get("pagoPoliza").get("poliza"), poliza);
         Predicate criterio1_2 = builder.equal(root.get("estatusActivo"), GenericEnumType.ENABLED);
         Predicate criterio2 = builder.equal(root.get("pagoPoliza").get("estatusActivo"), GenericEnumType.ENABLED);
         Predicate criterio3 = builder.equal(root.get("pagoPoliza").get("pagoEstatus"), GenericEnumType.ENABLED);
         Predicate criterio4 = builder.equal(root.get("pagoPoliza").get("pagoEstatus"), GenericEnumType.INCOMPLETE);

         criteria.where(builder.and(criterio1, criterio1_2, criterio2), builder.or(criterio3, criterio4));

         resultList = session.createQuery(criteria).getResultList();

         logger.info("Total of pagos found: " + (null == resultList ? -1 : resultList.size()));
         transaction.commit();
      } catch (HibernateException e) {
         logger.error("Pagos cannot be loaded ");
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method fillPagosPolizaByPoliza ", e);
         rollback(transaction);
      }
      return resultList;
   }

   public boolean updatePago(PagosPoliza pago, User user) {
      logger.info("addPagos");
      boolean success = false;
      Transaction transaction = null;

      try {
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();

         pago.setLastUpdatedOn(correcciónDeHr(new Date(), -7));
         pago.setLastUpdatedBy(user);
         session.update(pago);
         transaction.commit();

         success = true;
      } catch (HibernateException e) {
         logger.error("Venta can not be added ", e);
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method addVenta() ", e);
         rollback(transaction);
      }
      return success;
   }

   public PagosPoliza getSiguintePagosPolizaARealizarByPoliza(Poliza poliza) {
      logger.info("getSiguintePagosPolizaARealizarByPoliza");
      PagosPoliza result = null;
      Transaction transaction = null;
      try {
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         CriteriaBuilder builder = session.getCriteriaBuilder();

         CriteriaQuery<PagosPoliza> criteria = builder.createQuery(PagosPoliza.class);
         Root<PagosPoliza> root = criteria.from(PagosPoliza.class);

         Predicate criterio1 = builder.equal(root.get("poliza"), poliza);
         Predicate criterio2 = builder.equal(root.get("estatusActivo"), GenericEnumType.ENABLED);
         Predicate criterio3 = builder.equal(root.get("pagoEstatus"), GenericEnumType.DISABLED);
         Predicate criterio4 = builder.equal(root.get("pagoEstatus"), GenericEnumType.INCOMPLETE);

         criteria.where(builder.and(criterio1, criterio2), builder.or(criterio3, criterio4));
         criteria.orderBy(builder.desc(root.get("fechaAPagar")));

         TypedQuery<PagosPoliza> query = session.createQuery(criteria);
         query.setMaxResults(1);
         result = query.getSingleResult();

         transaction.commit();
      } catch (NoResultException e) {
         logger.info("No se encontró el pago: " + poliza.getFolio());
         rollback(transaction);
      } catch (HibernateException e) {
         logger.error("Error al obtener el pago " + poliza.getFolio(), e);
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Error en getSiguintePagosPolizaARealizarByPoliza(" + poliza.getFolio() + ")", e);
         rollback(transaction);
      }
      return result;
   }

   public boolean createDetellePago(DetellePagoPoliza dpago, User user) {
      logger.info("createDetellePago");
      boolean success = false;
      Transaction transaction = null;

      try {
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         dpago.setId(UUID.randomUUID().toString());
         dpago.setCreatedOn(correcciónDeHr(new Date(), -7));
         dpago.setCreatedBy(user);
         session.save(dpago);
         transaction.commit();

         success = true;
      } catch (HibernateException e) {
         logger.error("Detalle pago can not be added ", e);
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method createDetellePago() ", e);
         rollback(transaction);
      }
      return success;
   }

   public boolean updateDetellePago(DetellePagoPoliza dpago, User user) {
      logger.info("createDetellePago");
      boolean success = false;
      Transaction transaction = null;

      try {
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         dpago.setLastUpdatedOn(correcciónDeHr(new Date(), -7));
         dpago.setLastUpdatedBy(user);
         session.update(dpago);
         transaction.commit();

         success = true;
      } catch (HibernateException e) {
         logger.error("Detalle pago can not be added ", e);
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method createDetellePago() ", e);
         rollback(transaction);
      }
      return success;
   }

   public DetellePagoPoliza getUltimoDetalleByPago(String idPago) {
      logger.info("getUltimoDetalleByPago");

      DetellePagoPoliza result = null;
      Transaction transaction = null;
      try {
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         CriteriaBuilder builder = session.getCriteriaBuilder();

         CriteriaQuery<DetellePagoPoliza> criteria = builder.createQuery(DetellePagoPoliza.class);
         Root<DetellePagoPoliza> root = criteria.from(DetellePagoPoliza.class);

         criteria.where(builder.equal(root.get("pagoPoliza").get("id"), idPago));
         criteria.orderBy(builder.desc(root.get("createdOn")));

         TypedQuery<DetellePagoPoliza> query = session.createQuery(criteria);
         query.setMaxResults(1);
         result = query.getSingleResult();

         transaction.commit();
      } catch (NoResultException e) {
         logger.info("No se encontró detalle para el pago: " + idPago);
         rollback(transaction);
      } catch (HibernateException e) {
         logger.error("Error al obtener el detalle del pago " + idPago, e);
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Error en getUltimoDetalleByPago(" + idPago + ")", e);
         rollback(transaction);
      }
      return result;
   }

   public List<DetellePagoPoliza> fillDetellePagoPolizaByPago(String idPago) {
      logger.info("fillDetellePagoPolizaByPago");
      List<DetellePagoPoliza> resultList = null;
      Transaction transaction = null;
      try {
         logger.info("Searching detalles pagos for poliza..");
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         CriteriaBuilder builder = session.getCriteriaBuilder();

         CriteriaQuery<DetellePagoPoliza> criteria = builder.createQuery(DetellePagoPoliza.class);
         Root<DetellePagoPoliza> root = criteria.from(DetellePagoPoliza.class);

         Predicate criterio1 = builder.equal(root.get("pagoPoliza").get("id"), idPago);
         Predicate criterio2 = builder.equal(root.get("estatusActivo"), GenericEnumType.ENABLED);

         criteria.where(builder.and(criterio1, criterio2));
         criteria.orderBy(builder.asc(root.get("createdOn")));

         resultList = session.createQuery(criteria).getResultList();

         logger.info("Total of detalles pagos found: " + (null == resultList ? -1 : resultList.size()));
         transaction.commit();
      } catch (HibernateException e) {
         logger.error("Detalles pagos cannot be loaded ");
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method fillDetellePagoPolizaByPago ", e);
         rollback(transaction);
      }
      return resultList;
   }

   public String procesarPagoMultiple(Poliza polizaActual, Double montoTotal, String metodoPago, String comentario, User user) {
      Session session = null;
      Transaction transaction = null;
      Date fechaActual = correcciónDeHr(new Date(), -7);
      List<String> foliosGenerados = new ArrayList<>();
      List<String> detallesPagos = new ArrayList<>();
      try {
         session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();

         // 1. Obtener pagos validos
         CriteriaBuilder builder = session.getCriteriaBuilder();
         CriteriaQuery<PagosPoliza> criteria = builder.createQuery(PagosPoliza.class);
         Root<PagosPoliza> root = criteria.from(PagosPoliza.class);

         Predicate[] predicates = new Predicate[]{
            builder.equal(root.get("poliza"), polizaActual),
            builder.equal(root.get("estatusActivo"), GenericEnumType.ENABLED),
            root.get("pagoEstatus").in(GenericEnumType.DISABLED, GenericEnumType.INCOMPLETE)
         };
         criteria.where(predicates).orderBy(builder.asc(root.get("fechaAPagar")));

         List<PagosPoliza> pagos = session.createQuery(criteria).getResultList();

         if (pagos.isEmpty()) {
            return "No existen pagos pendientes";
         }

         // 2. Deuda total
         BigDecimal deudaTotal = pagos.stream().map(p -> BigDecimal.valueOf(p.getPago()).subtract(BigDecimal.valueOf(p.getCantidadPagada()))).reduce(BigDecimal.ZERO, BigDecimal::add);
         BigDecimal montoPagadoBD = BigDecimal.valueOf(montoTotal);
         BigDecimal tolerancia = new BigDecimal("0.10");
         if (montoPagadoBD.subtract(deudaTotal).compareTo(tolerancia) > 0) {
            return "Monto excede tolerancia permitida";
         }

         // 3. Procesar pagos
         double montoRestante = montoTotal;

         // 5. Generar folio
         // 5.1. Obtener el último consecutivo
         Integer ultimoConsecutivo = obtenerUltimoConsecutivo("A-PRASE");
         if (ultimoConsecutivo == null) {
            return "Error al generar los folios";
         }
         // 5.2. Modificar el ciclo para usar incremento local
         int currentConsecutivo = ultimoConsecutivo + 1;

         //4. Crear historial
         HistorialAbonoMultiple historial = new HistorialAbonoMultiple();
         historial.setId(UUID.randomUUID().toString());
         historial.setLocation(polizaActual.getLocation());
         historial.setEstatus(EstatusPagoMultiple.PROCESANDO);
         historial.setPoliza(polizaActual);
         historial.setCobro(user);
         historial.setFechaPago(fechaActual);
         historial.setMetodoPago(MetodoPago.valueOf(metodoPago));
         historial.setTotalPago(montoTotal);
         historial.setCometario(comentario);
         historial.setCreatedOn(fechaActual);
         historial.setCreatedBy(user);
         session.save(historial);

         for (PagosPoliza pago : pagos) {
            if (montoRestante <= 0) {
               break;
            }

            double saldoPendiente = pago.getPago() - pago.getCantidadPagada();
            if (saldoPendiente <= 0) {
               continue;
            }

            double montoAplicar = Math.min(saldoPendiente, montoRestante);

            // 5.3 Generar folio con incremento local
            String folio = obtenerFolio("A-PRASE", currentConsecutivo);
            currentConsecutivo++;

            // 6. Crear detalle de pago
            DetellePagoPoliza detalle = new DetellePagoPoliza();
            detalle.setId(UUID.randomUUID().toString());
            detalle.setPagoPoliza(pago);
            detalle.setEstatusActivo(GenericEnumType.ENABLED);
            detalle.setEstatusPago(GenericEnumType.ENABLED);
            detalle.setTipoPago(TipoAbono.ABONO);
            detalle.setFechaPago(fechaActual);
            detalle.setCantidadPagada(montoAplicar);
            detalle.setCobro(user);
            detalle.setFolio(folio);
            detalle.setMetodoPago(MetodoPago.valueOf(metodoPago));
            detalle.setCreatedOn(fechaActual);
            detalle.setCreatedBy(user);
            detalle.setHistorialAbonoMultiple(historial);
            session.save(detalle);

            // 7. Actualizar pago
            pago.setCantidadPagada(pago.getCantidadPagada() + montoAplicar);
            pago.setDiferenciaPago(pago.getPago() - pago.getCantidadPagada());
            if (pago.getDiferenciaPago() < 0.01) {
               pago.setPagoEstatus(GenericEnumType.ENABLED);
               pago.setCantidadPagada(pago.getPago());
            } else {
               pago.setPagoEstatus(GenericEnumType.INCOMPLETE);
            }
            pago.setLastUpdatedOn(fechaActual);
            pago.setLastUpdatedBy(user);
            session.update(pago);

            // 8. Actualizar la cantidad pagada de la poliza
            polizaActual.setCantidadPagada(polizaActual.getCantidadPagada() + montoAplicar);
            montoRestante -= montoAplicar;

            // 9. Se agrega el folio a la lista
            foliosGenerados.add(folio);

            // 10. Se crear el detalle del folio
            String detallePago = String.format(
                    "%s | ",
                    folio
            );
            detallesPagos.add(detallePago);
         }

         // 11. Validar liquidación completa de la poliza
         if (Math.abs(polizaActual.getAmount() - polizaActual.getCantidadPagada()) < 0.10) {
            polizaActual.setEstatus(PolizaEstatus.LIQUIDADO);
         }
         polizaActual.setLastUpdatedOn(fechaActual);
         polizaActual.setLastUpdatedBy(user);
         session.update(polizaActual);

         // 12. Se actualizar historial con los folios
         historial.setFolios(String.join(", ", foliosGenerados));
         historial.setDetalle(String.join("\n", detallesPagos));
         historial.setEstatus(EstatusPagoMultiple.PAGADO);
         session.update(historial);

         transaction.commit();
         return historial.getId();
      } catch (Exception e) {
         if (transaction != null) {
            transaction.rollback();
         }
         return "Error interno: " + e.getMessage();
      } finally {
         if (session != null) {
            session.close();
         }
      }
   }

   private Integer obtenerUltimoConsecutivo(String ab) {
      Session session = null;
      try {
         session = HibernateUtil.getSessionFactory().openSession();

         String query = "SELECT tap FROM DetellePagoPoliza tap "
                 + "WHERE tap.folio LIKE '" + ab + "%' "
                 + "ORDER BY "
                 + "SUBSTRING_INDEX(tap.folio, '-', 1) DESC, "
                 + "SUBSTRING_INDEX(SUBSTRING_INDEX(tap.folio, '-', -2), '-', -6) DESC ";

         DetellePagoPoliza ultimoFolio = (DetellePagoPoliza) session.createQuery(query).setMaxResults(1).uniqueResult();

         if (ultimoFolio == null || ultimoFolio.getFolio() == null) {
            return 0;
         }

         String[] partes = ultimoFolio.getFolio().split("-");
         if (partes.length < 4) {
            throw new IllegalStateException("Formato inválido. Se esperaba: ABREV-CONSECUTIVO-AÑO");
         }

         try {
            return Integer.valueOf(partes[2]);
         } catch (NumberFormatException e) {
            throw new IllegalStateException("Consecutivo no numérico: " + partes[2]);
         }

      } catch (Exception e) {
         logger.error("Error obteniendo consecutivo: ", e);
         return null;
      } finally {
         if (session != null) {
            session.close();
         }
      }
   }

   private String obtenerFolio(String ab, int consecutivo) {
      try {
         String numeroFormateado = String.format("%06d", consecutivo);
         Calendar calendario = Calendar.getInstance();
         int ano = calendario.get(Calendar.YEAR);
         return ab + "-" + numeroFormateado + "-" + ano;
      } catch (Exception e) {
         logger.error("Error generando folio", e);
         return null;
      }
   }

   public HistorialAbonoMultiple getHistorialAbonoMultipleById(String userId) {
      logger.info("getHistorialAbonoMultipleById");

      HistorialAbonoMultiple general = null;
      Transaction transaction = null;
      try {
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();

         general = session.get(HistorialAbonoMultiple.class, userId);

         transaction.commit();
         logger.info("HistorialAbonoMultiple " + general);
      } catch (HibernateException e) {
         logger.error("HistorialAbonoMultiple cannot be loaded " + userId, e);
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method getHistorialAbonoMultipleById(" + userId + ") ", e);
         rollback(transaction);
      }
      return general;
   }

   public boolean createSolicitudDevolucionDetellePagoPoliza(SolicitudDevolucionDetellePagoPoliza modelo, User user) {
      logger.info("createDetellePago");
      boolean success = false;
      Transaction transaction = null;
      try {
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         modelo.setId(UUID.randomUUID().toString());
         modelo.setCreatedOn(correcciónDeHr(new Date(), -7));
         modelo.setCreatedBy(user);
         session.save(modelo);
         transaction.commit();
         success = true;
      } catch (HibernateException e) {
         logger.error("Detalle pago can not be added ", e);
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method createDetellePago() ", e);
         rollback(transaction);
      }
      return success;
   }

   public List<SolicitudDevolucionDetellePagoPoliza> fillSolicitudDevolucionDetellePagoPolizaByLocation(String idLocation) {
      logger.info("fillSolicitudDevolucionDetellePagoPolizaByLocation");
      List<SolicitudDevolucionDetellePagoPoliza> resultList = null;
      Transaction transaction = null;
      try {
         logger.info("Searching solicitudes..");
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         CriteriaBuilder builder = session.getCriteriaBuilder();

         CriteriaQuery<SolicitudDevolucionDetellePagoPoliza> criteria = builder.createQuery(SolicitudDevolucionDetellePagoPoliza.class);
         Root<SolicitudDevolucionDetellePagoPoliza> root = criteria.from(SolicitudDevolucionDetellePagoPoliza.class);

         Predicate criterio1 = builder.equal(root.get("location").get("id"), idLocation);
         Predicate criterio2 = builder.equal(root.get("estatusActivo"), GenericEnumType.ENABLED);

         criteria.where(builder.and(criterio1, criterio2));
         criteria.orderBy(builder.asc(root.get("createdOn")));

         resultList = session.createQuery(criteria).getResultList();

         logger.info("Total of solicitudes found: " + (null == resultList ? -1 : resultList.size()));
         transaction.commit();
      } catch (HibernateException e) {
         logger.error("solicitudes cannot be loaded ");
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method fillSolicitudDevolucionDetellePagoPolizaByLocation ", e);
         rollback(transaction);
      }
      return resultList;
   }

   public List<SolicitudDevolucionDetellePagoPoliza> fillSolicitudesPedientesByDetallePago(String idPago) {
      logger.info("fillSolicitudesPedientesByDetallePago");
      List<SolicitudDevolucionDetellePagoPoliza> resultList = null;
      Transaction transaction = null;
      try {
         logger.info("Searching solicitudes..");
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         CriteriaBuilder builder = session.getCriteriaBuilder();

         CriteriaQuery<SolicitudDevolucionDetellePagoPoliza> criteria = builder.createQuery(SolicitudDevolucionDetellePagoPoliza.class);
         Root<SolicitudDevolucionDetellePagoPoliza> root = criteria.from(SolicitudDevolucionDetellePagoPoliza.class);

         Predicate criterio1 = builder.equal(root.get("detallePagoSolicitud").get("id"), idPago);
         Predicate criterio2 = builder.equal(root.get("estatusActivo"), GenericEnumType.ENABLED);
         Predicate criterio3 = builder.equal(root.get("estatusSolicitud"), EstatusSolicitud.PENDIENTE);

         criteria.where(builder.and(criterio1, criterio2));
         criteria.orderBy(builder.asc(root.get("createdOn")));

         resultList = session.createQuery(criteria).getResultList();

         logger.info("Total of solicitudes found: " + (null == resultList ? -1 : resultList.size()));
         transaction.commit();
      } catch (HibernateException e) {
         logger.error("solicitudes cannot be loaded ");
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method fillSolicitudesPedientesByDetallePago ", e);
         rollback(transaction);
      }
      return resultList;
   }

   public boolean rechazarSolicitudDevolucionDetellePagoPoliza(SolicitudDevolucionDetellePagoPoliza modelo, User user) {
      logger.info("rechazarSolicitudDevolucionDetellePagoPoliza");
      boolean success = false;
      Transaction transaction = null;
      try {
         Session session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         modelo.setEstatusSolicitud(EstatusSolicitud.CANCELADO);
         modelo.setLastUpdatedOn(correcciónDeHr(new Date(), -7));
         modelo.setLastUpdatedBy(user);
         session.update(modelo);
         transaction.commit();
         success = true;
      } catch (HibernateException e) {
         logger.error("Solicitud Devolucion Detelle Pago Poliza can not be added: ", e);
         rollback(transaction);
      } catch (Exception e) {
         logger.error("Method rechazarSolicitudDevolucionDetellePagoPoliza() ", e);
         rollback(transaction);
      }
      return success;
   }

   public Boolean aprobarSolicitudDevolucionDetellePagoPoliza(SolicitudDevolucionDetellePagoPoliza solicitud, User user) {
      logger.info("aprobarSolicitudDevolucionDetellePagoPoliza()");
      Session session = null;
      Transaction transaction = null;
      Date fechaActual = correcciónDeHr(new Date(), -7);
      try {
         session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         // 1.- se carga el detalle
         DetellePagoPoliza detalleOriginal = solicitud.getDetallePagoSolicitud();

         // 2. Actualizar detalle original
         detalleOriginal.setEstatusPago(GenericEnumType.CANCELED);
         detalleOriginal.setLastUpdatedOn(fechaActual);
         detalleOriginal.setLastUpdatedBy(user);
         session.update(detalleOriginal);

         // 3. Crear detalle de devolución
         DetellePagoPoliza detalleDevolucion = new DetellePagoPoliza();
         detalleDevolucion.setId(UUID.randomUUID().toString());
         detalleDevolucion.setPagoPoliza(solicitud.getDetallePagoSolicitud().getPagoPoliza());
         detalleDevolucion.setEstatusActivo(GenericEnumType.ENABLED);
         detalleDevolucion.setEstatusPago(GenericEnumType.ENABLED);
         detalleDevolucion.setTipoPago(TipoAbono.DEVOLUCION);
         detalleDevolucion.setFechaPago(fechaActual);
         detalleDevolucion.setCantidadPagada(solicitud.getMontoDevolucion());
         detalleDevolucion.setCobro(solicitud.getUsuarioCorte());
         detalleDevolucion.setFolio("");
         detalleDevolucion.setMetodoPago(MetodoPago.EFECTIVO);
         detalleDevolucion.setCreatedOn(fechaActual);
         detalleDevolucion.setCreatedBy(user);
         detalleDevolucion.setHistorialAbonoMultiple(null);
         session.save(detalleDevolucion);

         // 4. Actualizar pago principal
         PagosPoliza pagoPrincipal = detalleOriginal.getPagoPoliza();
         pagoPrincipal.setCantidadPagada(pagoPrincipal.getCantidadPagada() - solicitud.getMontoDevolucion());
         pagoPrincipal.setDiferenciaPago(pagoPrincipal.getPago() - pagoPrincipal.getCantidadPagada());
         // 5. Validar estado del pago
         double nuevaDiferencia = pagoPrincipal.getPago() - pagoPrincipal.getCantidadPagada();
         if (Math.abs(nuevaDiferencia) > 0.01) {
            pagoPrincipal.setPagoEstatus(GenericEnumType.INCOMPLETE);
         } else {
            pagoPrincipal.setPagoEstatus(GenericEnumType.DISABLED);
            pagoPrincipal.setCantidadPagada(0.0);
         }
         pagoPrincipal.setLastUpdatedOn(fechaActual);
         pagoPrincipal.setLastUpdatedBy(user);
         session.update(pagoPrincipal);

         // 6. Actualizar póliza
         Poliza poliza = pagoPrincipal.getPoliza();
         poliza.setCantidadPagada(poliza.getCantidadPagada() - solicitud.getMontoDevolucion());
         // 7. Validar estado de la póliza
         if (poliza.getEstatus() == PolizaEstatus.LIQUIDADO && (poliza.getAmount() - poliza.getCantidadPagada()) > 0.10) {
            poliza.setEstatus(PolizaEstatus.PENDIENTE);
         }
         poliza.setLastUpdatedOn(fechaActual);
         poliza.setLastUpdatedBy(user);
         session.update(poliza);

         // 8. Registrar solicitud de devolución
         solicitud.setEstatusSolicitud(EstatusSolicitud.APROBADO);
         solicitud.setDetallePagoDevolucion(detalleDevolucion);
         solicitud.setLastUpdatedOn(fechaActual);
         solicitud.setLastUpdatedBy(user);
         session.update(solicitud);

         transaction.commit();
         return true;

      } catch (Exception e) {
         if (transaction != null) {
            transaction.rollback();
         }
         logger.error("aprobarSolicitudDevolucionDetellePagoPoliza(): ", e);
         return false;
      } finally {
         if (session != null) {
            session.close();
         }
      }
   }

   public boolean editarPolizaModoPago(Poliza selectedPoliza, int numeroPagos, List<Calendar> fechasPago, List<DetellePagoPoliza> detallesPagosRealizados, List<PagosPoliza> pagos, User user) {
      logger.info("editarPolizaModoPago()");
      Date fechaActual = correcciónDeHr(new Date(), -7);
      Session session = null;
      Transaction transaction = null;
      try {
         session = HibernateUtil.getSessionFactory().getCurrentSession();
         transaction = session.beginTransaction();
         // 1. Actualizar póliza
         selectedPoliza.setLastUpdatedBy(user);
         selectedPoliza.setLastUpdatedOn(fechaActual);
         session.update(selectedPoliza);
         // 2. Obtener y desactivar pagos existentes

         for (PagosPoliza pago : pagos) {
            pago.setEstatusActivo(GenericEnumType.DISABLED);
            pago.setLastUpdatedBy(user);
            pago.setLastUpdatedOn(fechaActual);
            session.update(pago);
         }
         // 3. Crear nuevos pagos
         double contador = selectedPoliza.getCantidadPagada() == 0 ? 2 : 1;
         for (Calendar fecha : fechasPago) {
            PagosPoliza nuevoPago = new PagosPoliza();
            nuevoPago.setId(UUID.randomUUID().toString());
            nuevoPago.setPoliza(selectedPoliza);
            nuevoPago.setEstatusActivo(GenericEnumType.ENABLED);
            nuevoPago.setTipoPago(TipoAbono.ABONO);
            nuevoPago.setFechaAPagar(fecha.getTime());
            nuevoPago.setCreatedBy(user);
            nuevoPago.setCreatedOn(fechaActual);
            if (contador == 1) {
               // Primer pago
               if (numeroPagos == 1) {
                  nuevoPago.setPago(selectedPoliza.getAmount());
                  nuevoPago.setCantidadPagada(selectedPoliza.getCantidadPagada());
                  nuevoPago.setDiferenciaPago(selectedPoliza.getAmount() - selectedPoliza.getCantidadPagada());
                  nuevoPago.setPagoEstatus(Math.abs(nuevoPago.getDiferenciaPago()) < 0.01 ? GenericEnumType.ENABLED : GenericEnumType.INCOMPLETE);
               } else {
                  nuevoPago.setPago(selectedPoliza.getCantidadPagada());
                  nuevoPago.setCantidadPagada(selectedPoliza.getCantidadPagada());
                  nuevoPago.setDiferenciaPago(0D);
                  nuevoPago.setPagoEstatus(GenericEnumType.ENABLED);
               }
               // Asociar pagos existentes
               for (DetellePagoPoliza pagosRalizado : detallesPagosRealizados) {
                  pagosRalizado.setPagoPoliza(nuevoPago);
                  pagosRalizado.setLastUpdatedBy(user);
                  pagosRalizado.setLastUpdatedOn(fechaActual);
                  session.update(pagosRalizado);
               }
               contador = 2;
            } else {
               // Pagos subsiguientes
               if (selectedPoliza.getCantidadPagada() == 0.0) {
                  nuevoPago.setPago(((selectedPoliza.getAmount() - selectedPoliza.getCantidadPagada())) / (numeroPagos));
               } else {
                  nuevoPago.setPago(((selectedPoliza.getAmount() - selectedPoliza.getCantidadPagada())) / (numeroPagos - 1));
               }
               nuevoPago.setCantidadPagada(0D);
               nuevoPago.setPagoEstatus(GenericEnumType.DISABLED);
            }
            session.save(nuevoPago);
         }
         transaction.commit();
         return true;
      } catch (Exception e) {
         if (transaction != null) {
            transaction.rollback();
         }
         logger.error("Error en editarPolizaModoPago: ", e);
         return false;
      } finally {
         if (session != null && session.isOpen()) {
            session.close();
         }
      }
   }

   private Date correcciónDeHr(Date fecha, int horas) {
      Calendar calendar = Calendar.getInstance();
      calendar.setTime(fecha);
      calendar.add(Calendar.HOUR_OF_DAY, horas);
      Date nuevaFecha = calendar.getTime();
      return nuevaFecha;
   }

}
