package com.dla.dda.services;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.hibernate.type.IntegerType;
import org.hibernate.type.LongType;
import org.hibernate.type.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.dla.dda.domain.constants.ErrorCodes;
import com.dla.dda.domain.constants.EstatusContrato;
import com.dla.dda.domain.contrato.BonoFees;
import com.dla.dda.domain.contrato.PeriodoFlat;
import com.dla.dda.domain.contrato.Rango;
import com.dla.dda.domain.contrato.TituloDDA;
import com.dla.dda.domain.exceptions.DLAIntegrationException;
import com.dla.dda.domain.exceptions.utils.ErrorUtils;
import com.dla.dda.persistence.dao.BaseDAO;
import com.dla.dda.persistence.model.DdaTContrato;
import com.dla.dda.persistence.model.DdaTContratoBonoRango;
import com.dla.dda.persistence.model.DdaTContratoPago;
import com.dla.dda.persistence.model.DdaTContratoPagoCateg;
import com.dla.dda.persistence.model.DdaTContratoPagoCosto;
import com.dla.dda.persistence.model.DdaTContratoPagoFlat;
import com.dla.dda.persistence.model.DdaTContratoPagoRango;
import com.dla.dda.persistence.model.DdaTContratoTitulo;
import com.dla.dda.transforms.FeesTransform;
import com.dla.dda.transforms.TitulosTransform;


@Repository
public class ContratoService {

	@Autowired
	private BaseDAO dao;

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

	/**
	 * @see Servicio que trae los contratos por estudio vigentes
	 * @param request
	 * @return
	 */
	public List<DdaTContrato> findContratos(Integer idEstudio) throws DLAIntegrationException {

		Type[] types = { IntegerType.INSTANCE, IntegerType.INSTANCE };

		List<DdaTContrato> response = null;

		try {
			response = dao.findListByQuery("findContratosVigentesByEstudio",
					new Object[] { idEstudio, EstatusContrato.VIGENTE.getId() }, types, DdaTContrato.class);
		} catch (Exception e) {
			logger.error("Error to findContratosVigentesByEstudio [{}] [{}]", idEstudio, e.getMessage(), e);
			ErrorCodes error = ErrorCodes.INFRASTRUCTURE;
			ErrorUtils.setMessage(error, e.getMessage());
			throw new DLAIntegrationException(error);
		}
		return response;
	}

	/**
	 * @see metodo que nos trae los fees de los contratos, indicando su contrato
	 *      pago y su tipo de fees, esto nos sirve como pivote para consultar lo
	 *      correspondiente por fees
	 * @param idContrato
	 * @return
	 * @throws DLAIntegrationException
	 */
	public List<DdaTContratoPago> findFeesByContratos(Long idContrato) throws DLAIntegrationException {

		Type[] types = { LongType.INSTANCE };

		List<DdaTContratoPago> response = null;

		try {
			response = dao.findListByQuery("findPagoByContrato", new Object[] { idContrato }, types,
					DdaTContratoPago.class);
		} catch (Exception e) {
			logger.error("Error to findPagoByContrato [{}] [{}]", idContrato, e.getMessage(), e);
			ErrorCodes error = ErrorCodes.INFRASTRUCTURE;
			ErrorUtils.setMessage(error, e.getMessage());
			throw new DLAIntegrationException(error);
		}
		return response;
	}

	/**
	 * @see Servicio que trae los contratos por estudio vigentes
	 * @param request
	 * @return
	 */
	public Map<String, List<Rango>> findPeriodosSuscripcionFees(Long idContratoPago) throws DLAIntegrationException {

		Type[] types = { LongType.INSTANCE };

		Map<String, List<Rango>> periodos = new HashMap<>();

		try {

			List<Object[]> response = dao.findListByQuery("findSuscriptorFeesByContratoPago",
					new Object[] { idContratoPago }, types, Object[].class);

			// Procesamos el resultado
			if (response != null) {

				logger.info(" total [{}]", response.size());

				// Revisamos cuantos objetos nos trae ( elementos)
				for (Object[] row : response) {

					// Llenar los datos que viene repetido

					DdaTContratoPagoRango pagoRango = null;

					DdaTContratoPagoCosto pagoCosto = null;

					DdaTContratoPagoCateg pagoCateg = null;

					// Hacemos el casting por el tipo de dato
					for (Object entity : row) {

						if (entity instanceof DdaTContratoPagoRango)
							pagoRango = (DdaTContratoPagoRango) entity;
						else if (entity instanceof DdaTContratoPagoCosto)
							pagoCosto = (DdaTContratoPagoCosto) entity;
						else if (entity instanceof DdaTContratoPagoCateg)
							pagoCateg = (DdaTContratoPagoCateg) entity;
					}

					// Nos traemos los periodos
					Map<String, Rango> periodo = FeesTransform.transform(pagoRango, pagoCosto, pagoCateg);

					// Agregamos los periodos ya calculados
					FeesTransform.addPeriodoSuscripcion(periodo, periodos);

				}

			}

			logger.info(" periodos [{}]", periodos.size());

		} catch (Exception e) {
			logger.error("Error to findPeriodosSuscripcionFees [{}] [{}]", idContratoPago, e.getMessage(), e);
			ErrorCodes error = ErrorCodes.INFRASTRUCTURE;
			ErrorUtils.setMessage(error, e.getMessage());
			throw new DLAIntegrationException(error);
		}

		return periodos;

	}

	/**
	 * @see Se trae los periodos del fees flat
	 * @param idContratoPago
	 * @return
	 * @throws DLAIntegrationException
	 */
	public Map<String, List<PeriodoFlat>> findPeriodosFlatFees(Long idContratoPago) throws DLAIntegrationException {

		//
		Type[] types = { LongType.INSTANCE };

		Map<String, List<PeriodoFlat>> periodos = new HashMap<>();

		try {

			List<Object[]> response = dao.findListByQuery("findFlatFeesByContratoPago", new Object[] { idContratoPago },
					types, Object[].class);

			// Procesamos el resultado
			if (response != null) {

				logger.info(" total [{}]", response.size());

				// Revisamos cuantos objetos nos trae ( elementos)
				for (Object[] row : response) {

					// Llenar los datos que viene repetido

					DdaTContratoPagoFlat pagoFlat = null;

					DdaTContratoPagoCateg categoria = null;

					for (Object entity : row) {

						if (entity instanceof DdaTContratoPagoFlat)
							pagoFlat = (DdaTContratoPagoFlat) entity;
						else if (entity instanceof DdaTContratoPagoCateg)
							categoria = (DdaTContratoPagoCateg) entity;
					}

					logger.info("[{}] [{}]", pagoFlat, categoria);

					// Nos traemos los periodos
					Map<String, List<PeriodoFlat>> subperiodo = FeesTransform.transform(pagoFlat, categoria);
					FeesTransform.addPeriodoFlat(subperiodo, periodos);

				}

			}

			logger.info(" periodos [{}]", periodos.size());

		} catch (Exception e) {
			logger.error("Error to findSuscripcionFees [{}] [{}]", idContratoPago, e.getMessage(), e);
			ErrorCodes error = ErrorCodes.INFRASTRUCTURE;
			ErrorUtils.setMessage(error, e.getMessage());
			throw new DLAIntegrationException(error);
		}

		return periodos;

	}

	public BonoFees findBonosFlatFees(Long idContrato) throws DLAIntegrationException {

		Type[] types = { LongType.INSTANCE };
		
		BonoFees bono = new BonoFees();

		Map<String, List<Rango>> bonos = new HashMap<>();

		try {
			
			List<DdaTContratoBonoRango> response = dao.findListByQuery("findBonosFlatFeesByContrato",
					new Object[] { idContrato }, types, DdaTContratoBonoRango.class);

			// Procesamos el resultado
			if (response != null) {

				logger.info(" total [{}]", response.size());

				// Revisamos cuantos objetos nos trae ( elementos)
				for (DdaTContratoBonoRango row : response) {
					Rango rango = new Rango(new Integer(row.getDesde()), new Integer(row.getHasta()), 
							new Double(row.getBono()));

					List<Rango> rangos = new ArrayList<Rango>();
					rangos.add(rango);
					
					bonos.put(row.getTipoPago(), rangos);
				}

			}

			logger.info(" periodos [{}]", bonos.size());

		} catch (Exception e) {
			logger.error("Error to find Bonos Fees [{}] [{}]", idContrato, e.getMessage(), e);
			ErrorCodes error = ErrorCodes.INFRASTRUCTURE;
			ErrorUtils.setMessage(error, e.getMessage());
			throw new DLAIntegrationException(error);
		}
		
		
		bono.setRangos(bonos);

		return bono;

	}

	/**
	 * @see Servicio que trae los estudios asignados por el contrato por
	 *      contrato
	 * @param request
	 * @return
	 * @throws DLAIntegrationException
	 */
	public List<TituloDDA> findTitulos(Long idContrato) throws DLAIntegrationException {

		Type[] types = { LongType.INSTANCE };

		List<TituloDDA> response = new ArrayList<>();

		try {

			List<Object[]> titulosDDA = dao.findListByQuery("findTitulosByContrato", new Object[] { idContrato }, types,
					Object[].class);

			// Procesamos el resultado
			if (titulosDDA != null) {

				// Revisamos cuantos objetos nos trae ( elementos)
				for (Object[] row : titulosDDA) {

					DdaTContratoTitulo titulo = null;
					DdaTContratoPagoCateg categoria = null;

					for (Object entity : row) {

						if (entity instanceof DdaTContratoTitulo)
							titulo = (DdaTContratoTitulo) entity;
						else if (entity instanceof DdaTContratoPagoCateg)
							categoria = (DdaTContratoPagoCateg) entity;
					}

					response.add(TitulosTransform.transform(titulo, categoria));
				}

			}

		} catch (Exception e) {
			logger.error("Error to findTitulos [{}] [{}]", idContrato, e.getMessage(), e);
			ErrorCodes error = ErrorCodes.INFRASTRUCTURE;
			ErrorUtils.setMessage(error, e.getMessage());
			throw new DLAIntegrationException(error);
		}
		return response;
	}

	/**
	 * @see metodo para traerse un contrato con base a su id
	 * @param idContrato
	 * @return
	 * @throws DLAIntegrationException
	 */
	public DdaTContrato getContrato(Long idContrato) throws DLAIntegrationException {

		DdaTContrato contrato = null;
		try {
			contrato = dao.read(idContrato, DdaTContrato.class);
		} catch (IOException e) {
			logger.error("Error to get contrato [{}] [{}]", idContrato, e.getMessage(), e);
			ErrorCodes error = ErrorCodes.INFRASTRUCTURE;
			ErrorUtils.setMessage(error, e.getMessage());
			throw new DLAIntegrationException(error);
		}

		return contrato;

	}

}
