/**
 * 
 */
package com.ejie.ad74a.service.impl;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ejie.ad74a.dao.CopagoAytoDao;
import com.ejie.ad74a.exception.BatchProcessException;
import com.ejie.ad74a.exception.ResourceNotFoundException;
import com.ejie.ad74a.model.Ayuntamiento;
import com.ejie.ad74a.model.Copago;
import com.ejie.ad74a.model.FacturacionAyuntamiento;
import com.ejie.ad74a.model.FormaPago;
import com.ejie.ad74a.model.Periodo;
import com.ejie.ad74a.model.PisoTutelado;
import com.ejie.ad74a.model.PisoTuteladoSolicitante;
import com.ejie.ad74a.model.ReciboAyto;
import com.ejie.ad74a.model.ReciboAytoPisoTutelado;
import com.ejie.ad74a.model.ReciboAytoResidente;
import com.ejie.ad74a.model.Solicitante;
import com.ejie.ad74a.model.TipoRelanzamientoFacturacionAyto;
import com.ejie.ad74a.service.CopagoAytoService;
import com.ejie.ad74a.service.CopagoService;
import com.ejie.ad74a.service.InformeService;
import com.ejie.ad74a.service.LiquidacionService;
import com.ejie.ad74a.service.PisoTuteladoService;
import com.ejie.ad74a.util.UtilDates;
import com.ejie.ad74a.util.UtilPrecios;
import com.ejie.ad74a.ws.client.televida.model.RespuestaType;

/**
 * Implementacion del servicio para el procesamiento y emision de facturas a
 * ayuntamientos
 * 
 * @author BICUGUAL
 *
 */
@Service
public class CopagoAytoServiceImpl implements CopagoAytoService {

	private static final Logger logger = LoggerFactory.getLogger(CopagoAytoServiceImpl.class);

	@Autowired
	CopagoService copService;

	@Autowired
	InformeService infoService;
	
	@Autowired
	PisoTuteladoService pisoService;

	@Autowired
	LiquidacionService liqService;

	@Autowired
	CopagoAytoDao copAytoDao;

	@Override
	public List<Ayuntamiento> getLstAyuntamientos() {

		List<Ayuntamiento> lstAyuntamientos = copAytoDao.findAyuntamientosConPisosTutelados();

		logger.info("Ayuntamientos encontrados {} ", lstAyuntamientos.size());

		return lstAyuntamientos.stream().filter(Ayuntamiento::hasFacturacionPendiente).collect(Collectors.toList());
	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
	public void generarFacturacionAyto(Ayuntamiento ayto) throws Exception {

		logger.debug("{} -------> dia: {} periodicidad: {} ultima factura: {} proxima factura: {}",
				null != ayto.getDescEs() ? ayto.getDescEs().toUpperCase() : "", ayto.getDiaFacturacion(),
				ayto.getPeriodicidad(), ayto.getFechaUltimaFacturacion(), ayto.getFechaProximaFacturacion());

		// Calcular periodo
		Periodo periodoCompleto = new Periodo(ayto.getFechaProximaFacturacion(),
				ayto.getFechaProximaFacturacion().plusMonths(ayto.getPeriodicidad()).plusDays(-1));

		// Obtener informacion para el calculo de importe de facturacion
		List<ReciboAytoPisoTutelado> lstRecibosAytoPisos = this.getLstRecibosAytoPisoTutelado(ayto, periodoCompleto);

		if (!lstRecibosAytoPisos.isEmpty()) {

			// Calcular sumatorio de importes para los residentes de los pisos
			BigDecimal sumImportes = this.calcularSumImporteTotalAyto(lstRecibosAytoPisos);

			// Guardar Facturacion Ayuntamiento
			FacturacionAyuntamiento factura = this
					.createFacturacionAyuntamiento(new FacturacionAyuntamiento(ayto, sumImportes));

			// Crear recibo para el Ayto (Excel)
			ReciboAyto reciboAyto = new ReciboAyto(ayto, lstRecibosAytoPisos,  UtilDates.formatDateToString(factura.getfEmision(), "dd-MM-yyyy"), factura.getReferenciaRecibo(), UtilPrecios.importeToStringSinEur(sumImportes, true));

			//Imprimir informe
			infoService.printInformeFacturacionAyto(reciboAyto);
			
			// Generar informe
			byte[] informe = infoService.generarInforme(reciboAyto);

			// Guardar informe
			copAytoDao.saveInformeFacturacion(factura.getIdFacturacion(), informe);

			logger.debug("RECIBO AYTO : {}", reciboAyto);

			// Generar liquidacion si la forma de pago asignada es diferente de “sin
			// liquidación (otrasvías)” y el importe es mayor de 0
			if (FormaPago.SIN_LIQUIDACION.getValue() != ayto.getFormaPago()
					&& BigDecimal.ZERO.compareTo(factura.getImporteFacturado()) < 0) {
				RespuestaType wsResponse = liqService.generateLiquidacionAyto(factura, ayto, false);
				
				if (!"000".equals(wsResponse.getCodRespuesta())) {
					throw new BatchProcessException("Error invocacion WS liquidaciones: " + wsResponse.getCodRespuesta() + " - " + wsResponse.getMensaje()  );
				}
				
			}

		} else {
			logger.debug("{} <------- No se han encontrado pisos tutelados con beneficiarios para: {}",
					null != ayto.getDescEs() ? ayto.getDescEs().toUpperCase() : "", periodoCompleto);
		}

	}

	@Override
	public FacturacionAyuntamiento getFacturaById(Long idFactura) {

		try {
			return copAytoDao.findFacturacionAyuntamientoById(idFactura);
		} catch (EmptyResultDataAccessException e) {
			throw new ResourceNotFoundException("No se ha encontrado una factura con id: " + idFactura);
		}

	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
	public void relanzarFacturacionAyto(FacturacionAyuntamiento factura,
			TipoRelanzamientoFacturacionAyto tipoRelanzamiento) throws Exception {

		// Obtener los datos del ayuntamiento
		Ayuntamiento ayto = copAytoDao.findAyuntamientoByIdFacturacion(factura.getIdFacturacion());
		


		// Calcular perido completo
		LocalDate fInicioPeriodo = UtilDates.formatYearMonthToLocalDateAtFirstDayOfMonth(
				factura.getAnyoDesde() + "" + UtilDates.formatNMesToString(factura.getMesDesde()));
		LocalDate fFinPeriodo = UtilDates.formatYearMonthToLocalDateAtLastDayOfMonth(
				factura.getAnyoHasta() + "" + UtilDates.formatNMesToString(factura.getMesHasta()));
		Periodo periodoCompleto = new Periodo(fInicioPeriodo, fFinPeriodo);

		// Cambiar fecha ultima facturacion para volver a calcular el importe para los mismos meses
		LocalDate ultimaFacturacion = fInicioPeriodo.minusMonths(1);
		ayto.setAnyoHastaUltimaFacturacion(ultimaFacturacion.getYear());
		ayto.setMesHastaUltimaFacturacion(ultimaFacturacion.getMonthValue());
		

		// Obtener informacion para el calculo de importe de facturacion
		List<ReciboAytoPisoTutelado> lstRecibosAytoPisos = this.getLstRecibosAytoPisoTutelado(ayto, periodoCompleto);

		if (!lstRecibosAytoPisos.isEmpty()) {

			// Calcular sumatorio de importes para los residentes de los pisos
			BigDecimal sumImportes = this.calcularSumImporteTotalAyto(lstRecibosAytoPisos);

			if (tipoRelanzamiento.equals(TipoRelanzamientoFacturacionAyto.LIQUIDACION)) {

				// Generar liquidacion si la forma de pago asignada es diferente de “sin
				// liquidación (otrasvías)” y el importe es mayor de 0
				if (FormaPago.SIN_LIQUIDACION.getValue() != ayto.getFormaPago()
						&& BigDecimal.ZERO.compareTo(factura.getImporteFacturado()) < 0) {
					liqService.generateLiquidacionAyto(factura, ayto, false);
				}
			} else if (tipoRelanzamiento.equals(TipoRelanzamientoFacturacionAyto.INFORME)) {
				// Crear recibo para el Ayto (Excel)
				ReciboAyto reciboAyto = new ReciboAyto(ayto, lstRecibosAytoPisos, UtilDates.formatDateToString(factura.getfEmision(), "dd-MM-yyyy"), factura.getReferenciaRecibo(),
						UtilPrecios.importeToStringSinEur(sumImportes, true));

				// Generar informe
				byte[] informe = infoService.generarInforme(reciboAyto);

				// Guardar informe
				copAytoDao.saveInformeFacturacion(factura.getIdFacturacion(), informe);
			}
		}
	}

	@Override
	public FacturacionAyuntamiento createFacturacionAyuntamiento(FacturacionAyuntamiento facturacion) {
		Long idFactura = copAytoDao.createFacturacionAyuntamiento(facturacion);
		return copAytoDao.findFacturacionAyuntamientoById(idFactura);
	}

	/**
	 * Dado un ayto y un periodo, devuelve la informacion de los pisos tutelados,
	 * los beneficiarios y sus copagos para realizar un posterior calculo del
	 * importe total a facturar en ese periodo y construir el Excel
	 * 
	 * @param ayto
	 * @param periodoCompleto
	 * @return
	 */
	private List<ReciboAytoPisoTutelado> getLstRecibosAytoPisoTutelado(Ayuntamiento ayto, Periodo periodoCompleto) {
		// 1. Obtener los pisos del ayuntamiento por periodicidad completa que hayan
		// tenido beneficiario
		List<PisoTutelado> lstPisos = this.getLstPisosTuteladosByIdAyuntamiento(ayto.getIdAyuntamiento(),
				periodoCompleto);
		List<ReciboAytoPisoTutelado> lstRecibosAytoPisos = new ArrayList<>();

		if (!lstPisos.isEmpty()) {

			// 2. Por cada piso obtener los solicitantes del periodo completo
			lstPisos.stream().forEach(pisoTutelado -> {
				List<ReciboAytoResidente> lstRecibosAytoResidentes = new ArrayList<>();

				// 2.1 Obtener los solicitantes del piso tutelado
				List<Solicitante> lstSolicitantes = this
						.getLstSolicitantesByIdPisoTutelado(pisoTutelado.getIdPisoTutelado(), periodoCompleto);

				// 2.2 Por cada solicitante
				lstSolicitantes.stream().forEach(solicitante -> {
					logger.debug("Solicitante {} - {}", solicitante.getIdSolicitante(), solicitante.getNombreCompleto());
					// 2.2.1 Por cada mes del periodo completo de facturacion obtener los copagos
					List<Copago> lstCopagosBeneficiario = new ArrayList<>();
					List<Copago> lstCopagosPublicos = new ArrayList<>();

					for (int i = 0; i < ayto.getPeriodicidad(); i++) {

						Long nMensualidad = Long.valueOf(i) + 1;
						Periodo mensualidad = new Periodo(ayto.getFechaProximaFacturacion().plusMonths(i),
								ayto.getFechaProximaFacturacion().plusMonths(nMensualidad).plusDays(-1));

						logger.debug("mensualidad {} --> {} - {}", nMensualidad, mensualidad.getfInicio(),
								mensualidad.getfFin());

						if (solicitante.getfBaja() == null || (solicitante.getfBaja() != null && (solicitante.getfBaja().isAfter(mensualidad.getfInicio()) || solicitante.getfBaja().equals(mensualidad.getfInicio())))) {
							//Setteo fechas de inicio y fin del piso del solicitante para la mensualidad
							PisoTutelado pisoTuteladoSolicitante = this.setFechasPisoTutelado(pisoTutelado, solicitante.getIdSolicitante(), mensualidad);
							
							//Si el solicitante ha estado de alta en un piso durante la mensualidad
							if (null != pisoTuteladoSolicitante) {
							
								// 2.2.1.1 Obtener la lista de copagos para la mensualidad
								lstCopagosBeneficiario
										.addAll(copService.getLstCopagosBeneficiario(mensualidad, solicitante, pisoTuteladoSolicitante));
								
								lstCopagosPublicos.addAll(copService.getLstCopagosPublicos(mensualidad, solicitante,
										lstCopagosBeneficiario, pisoTuteladoSolicitante));
							}
						}
					}

					// 2.2.2 Agrupar copagos mensuales
					List<Copago> lstCopagosFusionados = copService.fusionarCopagosSolicitante(lstCopagosBeneficiario,
							lstCopagosPublicos);

					// Insertar recibo del residente (Excel) si tiene copagos
					if (!lstCopagosFusionados.isEmpty()) {
						lstRecibosAytoResidentes.add(new ReciboAytoResidente(solicitante.getNombreCompleto(),
								solicitante.getIdenTitular(), lstCopagosFusionados));
					}

				});

				// Insertar los recibos de residentes del piso (Excel) si tiene residentes
				if (!lstRecibosAytoResidentes.isEmpty()) {
					lstRecibosAytoPisos
							.add(new ReciboAytoPisoTutelado(pisoTutelado.getDenominacion(), lstRecibosAytoResidentes));
				}

			});
		}

		return lstRecibosAytoPisos;
	}

	/**
	 * Devuelve la lista de pisos tutelados para un ayuntamiento que estando de alta
	 * durante una mensualidad, estan siendo utilizados por algun beneficiario
	 * 
	 * @param idAyuntamiento
	 * @param periodo
	 * @return
	 */
	private List<PisoTutelado> getLstPisosTuteladosByIdAyuntamiento(Long idAyuntamiento, Periodo periodo) {
		return copAytoDao.findPisosTuteladosByIdAyuntamientoAndMensualidad(idAyuntamiento, periodo.getFInicioAsDate(),
				periodo.getFFinAsDate());
	}

	/**
	 * Devuelve la lista de solicitantes para un piso tutelado que han estado de
	 * alta durante algun momento de la mensualidad sin precios publicos, ni de
	 * beneficiario
	 * 
	 * @param idPisoTutelado
	 * @param periodo
	 * @return
	 */
	private List<Solicitante> getLstSolicitantesByIdPisoTutelado(Long idPisoTutelado, Periodo peridodoCompleto) {
		return copAytoDao.findSolicitantesByIdPisoTuteladoAndMensualidad(idPisoTutelado,
				peridodoCompleto.getFInicioAsDate(), peridodoCompleto.getFFinAsDate());

	}

	/**
	 * Obtiene la lista de pisos tutelados en los que ha estado dado de alta el solicitante en una mensualidad. 
	 * Solo deberiamos de encontrar un registro ya que funcionalmente nos indican que no puede haber mas de un cambio en un mismo mes
	 * 
	 * @param pisoTutelado
	 * @param idSolicitante
	 * @param mensualidad
	 * @return
	 */
	private PisoTutelado setFechasPisoTutelado(PisoTutelado pisoTutelado, Long idSolicitante, Periodo mensualidad) {
		
		List<PisoTuteladoSolicitante> lstPisosTuteladosSolicitante = pisoService.getLstPisosTuteladosSolicitanteByIdPisoAndIdSolicitanteAndMensualidad(pisoTutelado.getIdPisoTutelado(), idSolicitante, mensualidad);
		logger.debug("lstpisosTutelados de {}: {} ", idSolicitante, lstPisosTuteladosSolicitante);
		if (!lstPisosTuteladosSolicitante.isEmpty()) {
						
			pisoTutelado.setfDesde(lstPisosTuteladosSolicitante.get(0).getfDesde());
			pisoTutelado.setfHasta(lstPisosTuteladosSolicitante.get(lstPisosTuteladosSolicitante.size() - 1).getfHasta());
		
			logger.debug("Conversion a pisoTutelado: {}", lstPisosTuteladosSolicitante);
			return pisoTutelado;
		}
		
		return null;
	} 
	
	/**
	 * Realiza el sumatorio total de los copagos de todos los residentes de los
	 * pisos del ayuntamiento
	 * 
	 * @param lstRecibosAytoPisos
	 * @return
	 */
	private BigDecimal calcularSumImporteTotalAyto(List<ReciboAytoPisoTutelado> lstRecibosAytoPisos) {
		return lstRecibosAytoPisos.stream().flatMap(direccion -> direccion.getLstResidentes().stream())
				.flatMap(residente -> residente.getLstCopagos().stream()).map(Copago::getImporte)
				.reduce(BigDecimal.ZERO, BigDecimal::add);
	}

}
