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

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

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

import com.ejie.ad74a.dao.CopagoBeneficiarioDao;
import com.ejie.ad74a.model.Copago;
import com.ejie.ad74a.model.Mensualidad;
import com.ejie.ad74a.model.Periodo;
import com.ejie.ad74a.model.Piso;
import com.ejie.ad74a.model.PisoTutelado;
import com.ejie.ad74a.model.PisoTuteladoSolicitante;
import com.ejie.ad74a.model.PrecioPublico;
import com.ejie.ad74a.model.Solicitante;
import com.ejie.ad74a.service.CopagoBeneficiarioService;
import com.ejie.ad74a.service.CopagoService;
import com.ejie.ad74a.service.PisoTuteladoService;
import com.ejie.ad74a.service.PrecioService;
import com.ejie.ad74a.util.UtilProperties;
import com.ejie.ad74a.util.constantes.ConstantesBatch.LiqValMapKey;

/**
 * Implementacion de la capa de servicio para los procesos de copago de
 * beneficiarios
 * 
 * @author BICUGUAL
 *
 */
@Service
public class CopagoBeneficiarioServiceImpl implements CopagoBeneficiarioService {

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

	@Autowired
	private CopagoBeneficiarioDao copBenDao;

	@Autowired
	private PrecioService precService;

	@Autowired
	private CopagoService copService;

	@Autowired
	private PisoTuteladoService pisoService;

	@Override
	@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.NOT_SUPPORTED, rollbackFor = Throwable.class)
	public List<Solicitante> getLstSolicitantes(String idProvincia, Periodo mensualidad) {

		List<Solicitante> lstSolicitantes = copBenDao.findSolicitantesByProvincia(idProvincia,
				mensualidad.getFInicioAsDate(), mensualidad.getFFinAsDate(), mensualidad.getfInicio().getYear(),
				mensualidad.getfFin().getMonthValue());

		PrecioPublico precioPublico = precService.getPrecioPublico(mensualidad.getFInicioAsDate(),
				mensualidad.getFFinAsDate());

		logger.info("Numero de solicitantes encontrados: {}", lstSolicitantes.size());

		if (!lstSolicitantes.isEmpty()) {

			return lstSolicitantes.stream().map(solicitante -> {
				solicitante.setPrecioPublico(precioPublico);
				solicitante.setImporte(this.calcularImporteSolicitante(mensualidad, solicitante));

				return solicitante;
				// Excluye solicitantes cuyo importe es null o no es mayor de 0
			}).filter(s -> (null != s.getImporte() && BigDecimal.ZERO.compareTo(s.getImporte()) < 0))
					.collect(Collectors.toList());
		}

		return lstSolicitantes;
	}

	/*
	 * Este metodo va en una nueva transaccion para que los registros de la tabla
	 * espejo persistan aunque la transaccion padre falle.
	 */
	@Override
	@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW, rollbackFor = Throwable.class)
	public void generateMensualidadesEspejo(List<Solicitante> lstSolicitantes, Periodo periodoCompleto,
			String idEjecucion, boolean modoPrueba) {
		// Generar mensualidades para los solicitantes
		List<Mensualidad> lstMensualidadesSolicitante = lstSolicitantes.stream()
				.map(solicitante -> new Mensualidad(solicitante.getIdSolicitante(),
						periodoCompleto.getfInicio().getYear(), periodoCompleto.getfInicio().getMonthValue(),
						solicitante.getImporte(), idEjecucion))
				.collect(Collectors.toList());

		logger.info("Numero de mensualidades a generar en tabla espejo: {}", lstMensualidadesSolicitante.size());

		/*- Borrar mensualidades ya generadas con el mismo idEjecucion (si hubiera), para que no se dupliquen */
		copBenDao.deleteMensualidadesEspejoEjecucion(idEjecucion);
		if (!lstMensualidadesSolicitante.isEmpty()) {
			// Guardar mensualidades
			copBenDao.createMensualidadesEspejoBatch(lstMensualidadesSolicitante,
					Integer.valueOf(UtilProperties.Props.BATCH_SIZE.val()), modoPrueba);
		}

	}

	@Override
	@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
	public void volcarMensualidadesEspejoAFinales(String idEjecucion) {
		int numVolcados = copBenDao.volcarMensualidadesEspejoAFinales(idEjecucion);
		logger.info("{} registros volcados a la tabla final de MENSUALIDADES", numVolcados);
	}

	@Override
	@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.NOT_SUPPORTED, rollbackFor = Throwable.class)
	public List<Solicitante> getSolicitantesConMensualidadEspejo(String idEjecucion, Periodo periodoCompleto) {
		return copBenDao.findSolicitantesConMensualidadEspejoByidEjecucion(idEjecucion,
				periodoCompleto.getfInicio().getYear(), periodoCompleto.getfInicio().getMonthValue());
	}

	@Override
	@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.NOT_SUPPORTED, rollbackFor = Throwable.class)
	public List<Solicitante> getLstSolicitantesConLiquidacionPendiente(String idProvincia) {
		List<Solicitante> lstSolicitantes = copBenDao.findSolicitantesConLiquidacionPendiente(idProvincia);
		logger.info("Numero de solicitantes con liquidaciones pendientes encontrados: {}", lstSolicitantes.size());
		if (!lstSolicitantes.isEmpty()) {
			return lstSolicitantes.stream().map(solicitante -> {
				Periodo mensualidad = new Periodo(solicitante.getAnyoMesMensualidad());
				solicitante.setImporte(this.calcularImporteSolicitante(mensualidad, solicitante));
				return solicitante;
				// Excluye solicitantes cuyo importe es null o no es mayor de 0
			}).filter(s -> (null != s.getImporte() && BigDecimal.ZERO.compareTo(s.getImporte()) < 0))
					.collect(Collectors.toList());
		}

		return lstSolicitantes;
	}

	@Override
	@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.NOT_SUPPORTED, rollbackFor = Throwable.class)
	public List<Solicitante> getLstSolicitantesConLiquidacionPendientePorMensualidades(String idEjecucion) {
		List<Solicitante> lstSolicitantes = copBenDao
				.findSolicitantesConLiquidacionPendientePorMensualidades(idEjecucion);
		if (!lstSolicitantes.isEmpty()) {
			return lstSolicitantes.stream().map(solicitante -> {
				Periodo mensualidad = new Periodo(solicitante.getAnyoMesMensualidad());
				solicitante.setImporte(this.calcularImporteSolicitante(mensualidad, solicitante));
				return solicitante;
				// Excluye solicitantes cuyo importe es null o no es mayor de 0
			}).filter(s -> (null != s.getImporte() && BigDecimal.ZERO.compareTo(s.getImporte()) < 0))
					.collect(Collectors.toList());
		}
		return lstSolicitantes;
	}

	@Override
	public Map<LiqValMapKey, Collection<String>> comprobarMensualidadesGeneradas(String idEjecucion) {
		/*
		 * [jbhg] Para no empeorar el rendimiento del proceso, las validaciones se
		 * realizan directamente contra la base de datos
		 */
		Map<LiqValMapKey, Collection<String>> result = new HashMap<>();
		result.put(LiqValMapKey.ERRORS, copBenDao.comprobarMensualidadesGeneradas(idEjecucion));
		// No se han definido avisos para esta operacion:
		result.put(LiqValMapKey.WARNINGS, new ArrayList<>());
		return result;
	}

	/**
	 * Calcula el importe total a pagar para un solicitante obteniendo la
	 * informacion necesaria para realizar el calculo: - precios privados, precio
	 * publico, estancia en piso tutelado
	 * 
	 * @param mensualidad
	 * @param solicitante
	 * @return
	 */
	private BigDecimal calcularImporteSolicitante(Periodo mensualidad, Solicitante solicitante) {
		logger.debug("-----------> calcularImporteSolicitante Solicitante {}", solicitante.getIdSolicitante());

		Piso pisoPrivado = null;
		// Si el solicitante ha estado de alta en piso tutelado algun dia del periodo
		if (null != solicitante.getPisoTutelado() && solicitante.getPisoTutelado().intValue() > 0) {

			pisoPrivado = this.getPisoPrivado(mensualidad, solicitante);
			// Si el solicitante ha estado toda la mensualidad en piso tutelado, no lo
			// tratamos
			if (null == pisoPrivado) {
				logger.debug("Uso piso tutelado. Periodo completo: {}", mensualidad);

				return null;
			} else {
				logger.debug("Uso piso tutelado. En piso privado {} {}", pisoPrivado.getfDesde(),
						pisoPrivado.getfHasta());
			}

		}

		// Obtener copagos beneficiario. No pasamos piso tutelado porque el tratamiento
		// para aytos es diferente al de solicitantes
		List<Copago> lstCopagosBeneficiario = copService.getLstCopagosBeneficiario(mensualidad, solicitante,
				pisoPrivado);

		// Obtener copago publico. No pasamos piso tutelado porque el tratamiento para
		// aytos es diferente al de solicitantes
		List<Copago> lstCopagosPublicos = copService.getLstCopagosPublicos(mensualidad, solicitante,
				lstCopagosBeneficiario, pisoPrivado);

		List<Copago> lstCopagos = new ArrayList<>();
		lstCopagos.addAll(lstCopagosBeneficiario);
		lstCopagos.addAll(lstCopagosPublicos);

		// logger copagos
		logger.debug("Copagos: {}", lstCopagos);

		// Sumar importes copago filtrando exentos
		return lstCopagos.stream().filter(copago -> !copago.isIndExento()).map(Copago::getImporte)
				.reduce(BigDecimal.ZERO, BigDecimal::add);
	}

	/**
	 * Aunque devuelve un Piso tutelado, el objeto esta cargado con las fechas con
	 * un periodo desde - hasta que ha permanecido en un piso privado. que excluye
	 * los dias que el solicitante ha estado en piso tutelado
	 * 
	 * @param mensualidad
	 * @param solicitante
	 * @return
	 */
	private Piso getPisoPrivado(Periodo mensualidad, Solicitante solicitante) {

		Piso pisoPrivado = new PisoTutelado(mensualidad.getfInicio(), mensualidad.getfFin());

		List<PisoTuteladoSolicitante> lstPisosTuteladosSolicitante = pisoService
				.getLstPisosTuteladosSolicitanteByIdSolicitanteAndMensualidad(solicitante.getIdSolicitante(),
						mensualidad);

		LocalDate fDesdePiso = lstPisosTuteladosSolicitante.get(0).getfDesde();
		LocalDate fHastaPiso = lstPisosTuteladosSolicitante.get(lstPisosTuteladosSolicitante.size() - 1).getfHasta();

		// Si la fecha de inicio del primer registro <= 01/mes y año tratados, quiere
		// decir que está en piso tutelado al comenzar la mensualidad.
		if (!fDesdePiso.isAfter(mensualidad.getfInicio())) {

			// Buscamos cuándo termina, comprobando la fecha fin del último registro

			// Si ésta es nula o >= último día/mes y año tratados, no se incluirá en la
			// facturación porque no ha estado en piso particular ningun dia del mes.
			if (null == fHastaPiso || !fHastaPiso.isBefore(mensualidad.getfFin())) {
				// continuar con el siguiente beneficiario
				return null;
			}
			// En caso contrario
			else {
				// asignar F1=fecha fin del último registro + 1 día
				pisoPrivado.setfDesde(fHastaPiso.plusDays(1));
				// F2=último día/mes y año tratados

				if (null != solicitante.getfBaja() && (solicitante.getfBaja().isBefore(mensualidad.getfFin())
						|| solicitante.getfBaja().isEqual(mensualidad.getfFin()))) {
					pisoPrivado.setfHasta(solicitante.getfBaja().minusDays(1));
				}
			}
		}
		// Si la fecha de inicio del primer registro > 01/mes y año tratados, quiere
		// decir que está en piso particular al comenzar la mensualidad. F1=01/mes y año
		// tratados. F2 = fecha inicio del primer registro – 1 día
		else {
			pisoPrivado.setfHasta(fDesdePiso.minusDays(1));
		}

		return pisoPrivado;
	}

}
