package com.ejie.ad74a.process;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.ejie.ad74a.exception.Ad74aValidarLiquidacionesError;
import com.ejie.ad74a.exception.BatchProcessException;
import com.ejie.ad74a.model.Periodo;
import com.ejie.ad74a.model.Procesos;
import com.ejie.ad74a.model.Solicitante;
import com.ejie.ad74a.service.CopagoBeneficiarioService;
import com.ejie.ad74a.service.LiquidacionService;
import com.ejie.ad74a.service.ProcesosService;
import com.ejie.ad74a.util.UtilDates;
import com.ejie.ad74a.util.constantes.ConstantesBatch;
import com.ejie.ad74a.util.constantes.ConstantesBatch.LiqValMapKey;
import com.ejie.ad74a.util.constantes.EstadosProcesosBatchEnum;
import com.ejie.ad74a.util.constantes.ProcesosBatchEnum;

/**
 * Proceso de generaci�n de los cargos mensuales a los beneficiarios por copago
 * del servicio de Teleasistencia. Recibe como parametro el mes de cargo en
 * formato numerico del 1 al 12 y el codigo de provincia
 * 
 * @author BICUGUAL
 *
 */
@Component(value = "copagoBeneficiarios")
public class CopagoBeneficiariosProcess implements IProcess {

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

	@Autowired
	private CopagoBeneficiarioService copBenService;

	@Autowired
	private LiquidacionService liqService;

	@Autowired
	private ProcesosService procesosService;

	/**
	 * Metodo de ejecucion del proceso.
	 * 
	 * @param arg[0]          : Codigo de provincia
	 * @param arg[1]          : ANIOMES de cargo. Formato YYYYMM. Si no existe mes
	 *                        actual -1
	 * @param arg[2]          : idProceso tabla AD74A98T00
	 * @param modoPrueba=true : [jbhg] si viene informado este par�metro, se
	 *                        realizar�n las inserciones en las tablas espejo y las
	 *                        comprobaciones correspondientes, pero no se realizar�n
	 *                        cambios en las tablas definitivas. Este par�metro
	 *                        puede ir en cualquier posicion
	 */
	@Override
	@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
	public void execute(String... args) throws BatchProcessException {
		Procesos proceso = new Procesos();
		try {
			if (logger.isInfoEnabled()) {
				logger.info("Params: {}", Arrays.asList(args).stream().collect(Collectors.joining(", ")));
			}
			// [jbhg] Comprobacion si el proceso debe ejecutarse en MODO PRUEBA
			final boolean modoPrueba = Arrays.asList(args).stream().anyMatch(ConstantesBatch.ARGUMENTO_MODO_PRUEBA::equalsIgnoreCase);
			// Quitar argumento "modoPrueba" si estaba entre la lista original de argumentos
			String[] argsWithoutModoPrueba = Arrays.asList(args).stream()
					.filter(((Predicate<String>) ConstantesBatch.ARGUMENTO_MODO_PRUEBA::equalsIgnoreCase).negate())
					.toArray(String[]::new);

			final String idProvincia = argsWithoutModoPrueba[0];
			final String anyoMes = argsWithoutModoPrueba.length >= 2 ? argsWithoutModoPrueba[1]
					: UtilDates.getPreviousToCurrentYearMonth();
			Periodo periodoCompleto = new Periodo(anyoMes);
			String idProceso = argsWithoutModoPrueba.length >= 2 ? argsWithoutModoPrueba[1] : null;
			String idEjecucion = UUID.randomUUID().toString();

			// Si se ha informado idProceso, buscarlo si existe en bbdd
			if (StringUtils.isBlank(idProceso)
					|| (null == (proceso = this.procesosService.find(new Procesos(Long.valueOf(idProceso)))))) {
				// Si no se ha informado idProceso o no se ha encontrado en bbdd:
				// Se crea nuevo proceso en la tabla AD74A98T00
				proceso = new Procesos();
				proceso.setEstado98(EstadosProcesosBatchEnum.PENDIENTE.getId());
				proceso.setUsuAlta98("AD74A");
				proceso.setTipoProceso98(ProcesosBatchEnum.COPAGO_BEN.getId());
				proceso.setProceso98(idProvincia + "_" + anyoMes + "_" + idEjecucion);
				proceso = this.procesosService.add(proceso);
			}
			// Ya se haya creado nuevo o se est� usando uno existente, se actualiza su
			// estado
			this.procesosService.actualizarAIniciado(proceso);

			logger.info("Ejecucion copagos provincia: {}, periodo del copago: {}", idProvincia, periodoCompleto);

			// Obtener solicitantes a procesar
			List<Solicitante> lstSolicitantes = copBenService.getLstSolicitantes(idProvincia, periodoCompleto);

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

			if (!lstSolicitantes.isEmpty()) {
				logger.info("idEjecucion : {}", idEjecucion);

				// Generar mensualidades
				copBenService.generateMensualidadesEspejo(lstSolicitantes, periodoCompleto, idEjecucion, modoPrueba);
				// Obtener solicitantes y mensualidades generados en tabla espejo
				List<Solicitante> lstSolicitantesConMensualidad = copBenService
						.getSolicitantesConMensualidadEspejo(idEjecucion, periodoCompleto);
				// Generar liquidaciones
				liqService.generateLiquidacionesEspejo(lstSolicitantesConMensualidad, false, idEjecucion, modoPrueba);

				// Valida los resultados de las tablas espejo, y si no es modo prueba, vuelca a
				// las tablas finales.
				this.validarYVolcar(modoPrueba, idEjecucion);
			}

			this.procesosService.actualizarAFinalizado(proceso);

		} catch (Exception e) {
			if (proceso != null && proceso.getIdProceso98() != null) {
				if (StringUtils.isNotBlank(e.getMessage())) {
					proceso.setError98(e.getMessage().substring(0,
							Math.min(e.getMessage().length(), 799))); /*- Maximo 800 en campo de bbdd */
				} else {
					proceso.setError98("Sin mensaje de error. Excepcion: " + e.getClass().getTypeName());
				}
				this.procesosService.actualizarAError(proceso);
			}
			throw new BatchProcessException(e);
		}

		logger.info("EL PROCESO HA ACABADO SIN ERRORES!");
	}

	/*
	 * Valida y vuelca a las tablas finales, si no se esta ejecutando el proceso en
	 * modo prueba.
	 */
	private void validarYVolcar(final boolean modoPrueba, String idEjecucion) {
		// Se realizan las validaciones necesarias para comprobar que se han generado
		// correctamente los datos
		Map<LiqValMapKey, Collection<String>> mapErrWarn = copBenService.comprobarMensualidadesGeneradas(idEjecucion);
		Map<LiqValMapKey, Collection<String>> mapAux = liqService.comprobarLiquidacionesGeneradas(idEjecucion);
		mapErrWarn.get(LiqValMapKey.ERRORS).addAll(mapAux.get(LiqValMapKey.ERRORS));
		mapErrWarn.get(LiqValMapKey.WARNINGS).addAll(mapAux.get(LiqValMapKey.WARNINGS));

		tracearErroresAvisos(mapErrWarn);
		if (CollectionUtils.isNotEmpty(mapErrWarn.get(LiqValMapKey.ERRORS))) {
			//Solo se lanza excepcion en caso de errores, no de avisos...
			throw new Ad74aValidarLiquidacionesError();
		}

		if (!modoPrueba) {
			logger.info("Se inician los volcados de las tablas espejo a las finales (modoPrueba = false)");
			copBenService.volcarMensualidadesEspejoAFinales(idEjecucion);
			liqService.volcarLiquidacionesEspejoAFinales(idEjecucion);
			logger.info("Los volcados a las tablas finales han acabado correctamente (modoPrueba = false)");
		} else {
			logger.info("No se han realizado los volcados a las tablas finales (modoPrueba = true)");
		}
	}

	//Metodo para sacar por consola los errores o avisos del proceso
	private void tracearErroresAvisos(Map<LiqValMapKey, Collection<String>> mapErrWarn) {
		if (mapErrWarn.get(LiqValMapKey.ERRORS).isEmpty()) {
			logger.warn("No se han producido errores!!");
		} else if (logger.isWarnEnabled()) {
			logger.warn("Se han producido los siguientes ERRORES:{}\r\n",
					String.format("%n[%s]", StringUtils.join(mapErrWarn.get(LiqValMapKey.ERRORS).toArray(), ",\r\n")));
		}
		if (mapErrWarn.get(LiqValMapKey.WARNINGS).isEmpty()) {
			logger.warn("No se han producido avisos!!");
		} else if (logger.isWarnEnabled()) {
			logger.warn("Se han producido los siguientes AVISOS:{}\r\n",
					String.format("%n[%s]", StringUtils.join(mapErrWarn.get(LiqValMapKey.WARNINGS).toArray(), ",\r\n")));
		}

	}

}
