/*
 * 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.GenericEnumType;
import com.arrebol.taxiservicios.model.enums.MetodoPago;
import com.arrebol.taxiservicios.model.enums.PolizaEstatus;
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 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> 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.notEqual(root.get("poliza").get("estatus"), PolizaEstatus.CANCELADO);
         Predicate criterio5 = builder.greaterThanOrEqualTo(root.get("poliza").get("endDate").as(LocalDate.class), localHoy);
         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, 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 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 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.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;
   }

   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;
   }

}
