
package com.mx.dla.dda.admin.presupuestos.bos;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.mx.dla.dda.admin.presupuestos.daos.PresupuestoDAO;
import com.mx.dla.dda.admin.presupuestos.dtos.AmortApartadoDTO;
import com.mx.dla.dda.admin.presupuestos.dtos.ApartadoPreBD;
import com.mx.dla.dda.admin.presupuestos.dtos.MontoTransferirDTO;
import com.mx.dla.dda.admin.presupuestos.dtos.PresupuestoApartadoDTO;
import com.mx.dla.dda.admin.presupuestos.dtos.PresupuestoDTO;
import com.mx.dla.dda.contrato.ws.dtos.DTPresupuestoOIReq;
import com.mx.dla.dda.ordenesinternas.dtos.PresupuestoOrdenDTO;
import com.mx.dla.dda.restClient.bos.DLARestClient;
import com.mx.dla.dda.restClient.bos.DLARestClientFactory;
import com.mx.dla.dda.restClient.constants.DLARestServices;
import com.mx.dla.global.bos.BaseBO;

@Service
public class PresupuestoBO extends BaseBO{

	private Boolean amortizable = false;
	
	
	
	@Autowired
	private PresupuestoDAO presupuestoDAO;
	
	@Autowired
	private DLARestClientFactory dlaRestClientFactory;
	
	
	public String almacenaMonto(int anio, double monto){
		try {
			presupuestoDAO.almacenaMonto(anio,monto);
			return "APLICADO";
		} catch (Exception e) {
			return "Fallo en inserción";
			// TODO: handle exception
			
		}
	} 
	
	
	public List<List<PresupuestoDTO>> getPresupuestos(){
		   List<List<PresupuestoDTO>> presupuesto = new ArrayList<List<PresupuestoDTO>>();		   		   
		   
		   List<PresupuestoApartadoDTO> apartados = presupuestoDAO.getApartados();		   		   
		   		   
		   //Se obtiene presupuestos amortizables y no amortizables
		   List<PresupuestoApartadoDTO> apartadosA  = new ArrayList<PresupuestoApartadoDTO>();
		   List<PresupuestoApartadoDTO> apartadosNA = new ArrayList<PresupuestoApartadoDTO>();
		   List<Long> idsA  = new ArrayList<Long>();
		   List<Long> idsNA = new ArrayList<Long>();
		   
		   for(PresupuestoApartadoDTO apartado : apartados)
		   {			 
			 if(apartado.getType().compareTo("Amortizable") == 0)
			 {
				 apartadosA.add(apartado);
				 idsA.add(apartado.getId());
			 }				 
		   	 else
		   	 {
		   		apartadosNA.add(apartado);
		   		idsNA.add(apartado.getId());
		   	 }		    				   				    			    	   			    			    			    	 
		   }
		   
		   if(!apartadosA.isEmpty())
		   {
			  amortizable = true;
			  List<PresupuestoDTO>   presupuestosBD  = presupuestoDAO.getPresupuestos(idsA);			  
			  presupuesto.add(this.calcularPresupuestos(apartadosA, presupuestosBD));
		   }		       		   		  
		   if(!apartadosNA.isEmpty())
		   {
			   List<PresupuestoDTO>   presupuestosBD  = presupuestoDAO.getPresupuestos(idsNA);			   
			   presupuesto.add(this.calcularPresupuestos(apartadosNA, presupuestosBD));
		   }			   
		   return presupuesto;
	}

	public List<List<PresupuestoDTO>> getPresupuestosSinComprometido(){
		List<List<PresupuestoDTO>> presupuesto = new ArrayList<List<PresupuestoDTO>>();		   		   
		   
		   List<PresupuestoApartadoDTO> apartados = presupuestoDAO.getApartados();		   		   
		   		   
		   //Se obtiene presupuestos amortizables y no amortizables
		   List<PresupuestoApartadoDTO> apartadosA  = new ArrayList<PresupuestoApartadoDTO>();
		   List<PresupuestoApartadoDTO> apartadosNA = new ArrayList<PresupuestoApartadoDTO>();
		   List<Long> idsA  = new ArrayList<Long>();
		   List<Long> idsNA = new ArrayList<Long>();
		   
		   for(PresupuestoApartadoDTO apartado : apartados)
		   {		    			    				  			   
			 if(apartado.getType().compareTo("Amortizable") == 0)
			 {
				 apartadosA.add(apartado);
				 idsA.add(apartado.getId());
			 }				 
		   	 else
		   	 {
		   		apartadosNA.add(apartado);
		   		idsNA.add(apartado.getId());
		   	 }		    				   				    			    	   			    			    			    	 
		   }
		   
		   if(!apartadosA.isEmpty())
		   {
			  amortizable = true;
			  List<PresupuestoDTO>   presupuestosBD  = presupuestoDAO.getPresupuestos(idsA);			  
			  presupuesto.add(this.calcularPresupuestosSinComprometido(apartadosA, presupuestosBD));
		   }		       		   		  
		   if(!apartadosNA.isEmpty())
		   {
			   List<PresupuestoDTO>   presupuestosBD  = presupuestoDAO.getPresupuestos(idsNA);			   
			   presupuesto.add(this.calcularPresupuestosSinComprometido(apartadosNA, presupuestosBD));
		   }		       		   
		   return presupuesto;
	}
	
	private List<PresupuestoDTO> calcularPresupuestos(List<PresupuestoApartadoDTO> apartados, List<PresupuestoDTO> presBD){		    		    
	    	List<Long> idApartados = new ArrayList<Long>();
	    	Map<Long, Long> idTemp = new HashMap<Long, Long>();
	    	
	    	//Se divide los apartados por año
			Map<Integer, List<PresupuestoApartadoDTO>> mapPre = PresupuestoUtil.getApartadosXAnio(apartados, presBD, idTemp); 							
			idApartados = new ArrayList<Long>(idTemp.values());

			//Se obtiene el monto comprometido por apartados
			List<AmortApartadoDTO> amortizacion = new ArrayList<AmortApartadoDTO>();
			if(idApartados.size() > 0 )
				amortizacion = presupuestoDAO.getApartadosAmortizacion(idApartados);
			Map<String, Double> apartadosAmor = PresupuestoUtil.getAmortXApartados(idApartados, amortizacion);                        
						
			//Se obtiene montos por presupuesto-anio
			List<AmortApartadoDTO> amortizables = presupuestoDAO.getAmortizables();
									
			List<PresupuestoDTO> presupuestos = PresupuestoUtil.getSumarizadoPresupuestos(apartadosAmor, mapPre, amortizables);       		    		    		    
		    return presupuestos;
	}

	private List<PresupuestoDTO> calcularPresupuestosSinComprometido(List<PresupuestoApartadoDTO> apartados, List<PresupuestoDTO> presBD){		    		        	 
    	Map<Long, Long> idTemp = new HashMap<Long, Long>();
    	
    	//Se divide los apartados por año
		Map<Integer, List<PresupuestoApartadoDTO>> mapPre = PresupuestoUtil.getApartadosXAnio(apartados, presBD, idTemp); 									
													
		Map<String, Double> temp = new HashMap<String, Double>();
		List<AmortApartadoDTO> temp1 = new ArrayList<AmortApartadoDTO>();		
		List<PresupuestoDTO> presupuestos = PresupuestoUtil.getSumarizadoPresupuestos(temp, mapPre, temp1);       		    		    		    
	    return presupuestos;
	}
				
	public Boolean getAmortizable() {
		return amortizable;
	}
	
	//********* se guardana apatados
	public String guardarApartados(List<PresupuestoApartadoDTO> apartados, Integer anio) throws Exception {
		String mensaje = "";

		Boolean valido = true;

		for (PresupuestoApartadoDTO apartado : apartados) {

			if (apartado.getIdPreApart() == -1 && apartado.getMontoAsignado() != null && apartado.getMontoAsignado().doubleValue() > 0.0) {

				if (apartado.getType().compareTo("Amortizable") == 0) {

					apartado.setFechaSAP(this.getFechaHoy());
					String codigoSAP = this.notificarSAP(apartado, anio.intValue(), "KBUD");
					if (codigoSAP == null){
						valido = false;
						mensaje = mensaje + apartado.getNombre() + ", ";
					}

				}

				if (valido) {
					presupuestoDAO.eliminarPrepApartadoPrevio(apartado.getId(), anio);
					presupuestoDAO.guardarApartado(apartado, anio);

				}

			}

		}

		if (mensaje.compareTo("") == 0)
			mensaje = null;
		return mensaje;
	}

	public void actualizarApartadosTmp(List<PresupuestoApartadoDTO> apartados, Integer anio) throws Exception {

		for (PresupuestoApartadoDTO apartado : apartados) {
			if (apartado.getType().compareTo("Amortizable") == 0)
				apartado.setFechaSAP(this.getFechaHoy());

			if (apartado.getIdPreApart() == -1 && apartado.getMontoAsignado() != null && apartado.getMontoAsignado().doubleValue() > 0.0) {
				presupuestoDAO.eliminarPrepApartadoPrevio(apartado.getId(), anio);
				presupuestoDAO.guardarApartado(apartado, anio);
			}
			else if (apartado.getIdPreApart() != -1 && apartado.getMontoAsignado() != null)
				presupuestoDAO.actualizarApartado(apartado);

		}

	}

	public List<PresupuestoDTO> obtenerPresptoXTipo(String tipo){
		List<PresupuestoDTO> presupuestos = new ArrayList<PresupuestoDTO>();
		List<PresupuestoApartadoDTO> apartados = presupuestoDAO.getApartadosXTipo(tipo);
		List<ApartadoPreBD> asignado           = presupuestoDAO.obtenerPresupuestoAsignado(apartados);
		List<ApartadoPreBD> comprometido       = presupuestoDAO.obtenerPresupuestoComprometido(tipo);				
		
		TreeMap presups = new TreeMap();
		
		for(ApartadoPreBD a : safeList(asignado))
		{
			PresupuestoDTO pto = new PresupuestoDTO();
			pto.setAnio(a.getAnio());
			pto.setMontoAsignado(Double.parseDouble(a.getMontoAsignado()));
			presups.put(a.getAnio(), pto);
		}
		
		for(ApartadoPreBD a : safeList(comprometido))
		{
			if(presups.containsKey(a.getAnio()))
			{
				PresupuestoDTO temp = (PresupuestoDTO)presups.get(a.getAnio());
				temp.setMontoComprometido(Double.parseDouble(a.getMontoAsignado()));
				presups.put(a.getAnio(), temp);
			}
			else
			{
				PresupuestoDTO pto = new PresupuestoDTO();
				pto.setAnio(a.getAnio());
				pto.setMontoComprometido(Double.parseDouble(a.getMontoAsignado()));
				presups.put(a.getAnio(), pto);
			}				
		}		
		
		Set    set = presups.entrySet();	      
	    Iterator i = set.iterator();	      
	    while(i.hasNext()) 
	    {
	       Map.Entry me = (Map.Entry)i.next();
	       presupuestos.add((PresupuestoDTO)me.getValue());
	    }
		
		return presupuestos;
	}

	public PresupuestoDTO obtenerApartadosPresptoXAnio(String tipo, int anio){
		PresupuestoDTO presupuesto = new PresupuestoDTO();
		List<PresupuestoApartadoDTO> apartados = presupuestoDAO.getApartadosXTipo(tipo);
		List<ApartadoPreBD> asignado           = presupuestoDAO.obtenerApartadosAsignado(apartados, anio);
		List<ApartadoPreBD> comprometido       = presupuestoDAO.obtenerApartadosComprometido(tipo,anio);				
							
		for(ApartadoPreBD a : safeList(asignado))
		{
			for(PresupuestoApartadoDTO ap : safeList(apartados))
			{						
				if(ap.getId().intValue() == a.getIdApartado().intValue())
				{
					ap.setFechaSAP(a.getFechaSap());
					ap.setIdPreApart(a.getIdPreApart());
					ap.setMontoAsignado(Double.parseDouble(a.getMontoAsignado()));
				}				
			}	   						
		}
		
		for(ApartadoPreBD a : safeList(comprometido))
		{
			for(PresupuestoApartadoDTO ap : safeList(apartados))
			{						
				if(ap.getId().intValue() == a.getIdApartado().intValue())				
				   ap.setMontoComprometido(Double.parseDouble(a.getMontoAsignado()));									
			}	   						
		}
				
		presupuesto.setApartados(apartados);
		Collections.sort(presupuesto.getApartados(), new Comparator<PresupuestoApartadoDTO>() {
		    public int compare(PresupuestoApartadoDTO o1, PresupuestoApartadoDTO o2) {
		        return o1.getNombre().compareTo(o2.getNombre());
		    }
		});
		if(tipo.equals("Amortizable"))
		{
			String valor = presupuestoDAO.getAmortizable(anio);
			valor = valor == null ? "0" : valor;
			presupuesto.setMontoAmortizable(Double.parseDouble(valor));
		}
		   
		return presupuesto;
	}
	
	

	public String redistribuirApartados(List<PresupuestoApartadoDTO> apartados, Integer anio) throws Exception{
		String msg = null;
		if(apartados.get(0).getType().compareTo("No Amortizable") == 0)
		{			
			
			logger.debug(apartados.toString());
			List<PresupuestoApartadoDTO> amortizables = new ArrayList<PresupuestoApartadoDTO>();
			
			apartados.get(0).setMontoAsignado(apartados.get(1).getMontoAsignado() - Math.abs(apartados.get(0).getMontoAsignado()));
			apartados.get(2).setMontoAsignado(apartados.get(3).getMontoAsignado() + Math.abs(apartados.get(2).getMontoAsignado()));

			amortizables.add(apartados.get(0));
			amortizables.add(apartados.get(2));
			this.actualizarApartadosTmp(amortizables, anio);
			this.guardarRedistribucionBD(apartados.get(1).getId(), apartados.get(3).getId(), String.valueOf(apartados.get(1).getMontoAsignado()),
			String.valueOf(apartados.get(3).getMontoAsignado()), String.valueOf(apartados.get(1).getMontoAsignado()-apartados.get(0).getMontoAsignado()));
		}
		else
		{									
			//Se actualiza el apartado origen			
			apartados.get(0).setMontoAsignado(apartados.get(0).getMontoAsignado());

			String idSAP = this.notificarSAP(apartados.get(0), anio.intValue(), "KBR0");

			if(idSAP == null)
			{	//Si la actualizacion del apartado origen no fue exitosa				
				msg = "No se realizo la redistribucion, ocurrio un error inesperado.";				
			}			
			else
			{   //Si la actualizacion del apartado origen fue exitosa se actualiza apartado destino
				idSAP = this.notificarSAP(apartados.get(2), anio.intValue(), "KBN0");
				
				//si la actualizacion del apartado destino hubo un error se actualiza el apartado origen para evitar inconsistencia
				if(idSAP == null)
				{					
					apartados.get(0).setMontoAsignado(apartados.get(0).getMontoAsignado()*-1);
					idSAP = this.notificarSAP(apartados.get(0), anio.intValue(), "KBN0");
					msg = "No se realizo la redistribucion, ocurrio un error inesperado.";
				}
				else
				{					
					List<PresupuestoApartadoDTO> amortizables = new ArrayList<PresupuestoApartadoDTO>();					
					apartados.get(0).setMontoAsignado(apartados.get(1).getMontoAsignado() - Math.abs(apartados.get(0).getMontoAsignado()));
					apartados.get(2).setMontoAsignado(apartados.get(3).getMontoAsignado() + Math.abs(apartados.get(2).getMontoAsignado()));
					
					amortizables.add(apartados.get(0));
					amortizables.add(apartados.get(2));
					this.actualizarApartadosTmp(amortizables, anio);
					
					this.guardarRedistribucionBD(apartados.get(1).getId(), apartados.get(3).getId(), String.valueOf(apartados.get(1).getMontoAsignado()),
					String.valueOf(apartados.get(3).getMontoAsignado()), String.valueOf(apartados.get(1).getMontoAsignado()-apartados.get(0).getMontoAsignado()));
				}
			}										
		}
		return msg;
	}

	
	public String redistribuirApartadosMontoDispo(List<PresupuestoApartadoDTO> apartados, Integer anio) throws Exception{
		String msg = null;
		if(apartados.get(0).getType().compareTo("Amortizable") == 0)
		{			
			//Se actualiza el apartado destino			
			apartados.get(0).setMontoAsignado(apartados.get(0).getMontoAsignado());

			String idSAP = this.notificarSAP(apartados.get(0), anio.intValue(), "KBN0");

			if(idSAP == null)
			{	//Si la actualizacion del apartado destino no fue exitosa				
				msg = "No se realizo la redistribucion, ocurrio un error inesperado.";				
			}			
			else
			{   			
				List<PresupuestoApartadoDTO> amortizables = new ArrayList<PresupuestoApartadoDTO>();					
				apartados.get(0).setMontoAsignado(apartados.get(1).getMontoAsignado() + Math.abs(apartados.get(0).getMontoAsignado()));
				
				amortizables.add(apartados.get(0));
				this.actualizarApartadosTmp(amortizables, anio);
				
				this.guardarRedistribucionBD( null, apartados.get(1).getId(), null, String.valueOf(apartados.get(1).getMontoAsignado()),
						String.valueOf(apartados.get(1).getMontoAsignado()-apartados.get(0).getMontoAsignado()));
			}										
		}
		return msg;
	}
	
	public String notificarSAP(PresupuestoApartadoDTO apartado, int anio, String tipoOperacion){
		String respuesta = null;
		//Se notifica a sap la redistribucion "KBUD"
		try
		{
			DTPresupuestoOIReq dTPresupuestoOIReq = new DTPresupuestoOIReq();
			DTPresupuestoOIReq.Registro registro  = new DTPresupuestoOIReq.Registro();
			PresupuestoOrdenDTO presupuestoDTO= new PresupuestoOrdenDTO();
			
			registro.setTipoOperacion(tipoOperacion);
	    	registro.setOrden(apartado.getIdSap() );
	    	registro.setEjercicio(String.valueOf(anio));
	    	registro.setImporte( String.format("%.2f", apartado.getMontoAsignado()) );
	    	registro.setMoneda("USD");		    			    			    		   
	    	
	    	dTPresupuestoOIReq.setRegistro(registro);
	    	
	    	logger.info("Se llama interfaz ORDENES_INTERNAS_PRESUPUESTO #7");
	    	logger.info("Presupuesto. {}", apartado);
	    	logger.info("tipoOperaion: {}, orden: {},  moneda:USD",tipoOperacion, apartado.getIdSap());
	    	logger.info("ejercicio: {}, importe:{}", anio, apartado.getMontoAsignado());	    	
	    	//String.format("%.2f", cantidad)
	    	DLARestClient c = dlaRestClientFactory.getClient(DLARestServices.ORDENES_INTERNAS_PRESUPUESTO);	    	
	    	
	    	logger.info("URL {}", c.getUri());
	    	
	    	presupuestoDTO  = c.get(dTPresupuestoOIReq, PresupuestoOrdenDTO.class);
	    		
	    	logger.info("Respuesta servicio {}", presupuestoDTO.getEstatus());
	    	
	    	if(presupuestoDTO.getEstatus().compareTo("OK") != 0)				
	    		respuesta = null;
	    	else
	    		respuesta = "OK";
		}catch(Exception e)
		{
			logger.error("Se produjo un error al llamar al servicio rest." + DLARestServices.ORDENES_INTERNAS_PRESUPUESTO.toString());
			logger.error("Error: {}",e);
			respuesta = null;
		}							
		return respuesta;						
	}
		
	public void guardarRedistribucionBD(Long apOrigen, Long apDestino, String montoOrign, String montoDestino, String montoReasignado) throws Exception{		
			MontoTransferirDTO monto = new MontoTransferirDTO(apOrigen, apDestino, montoOrign, montoDestino, montoReasignado, new Date());
			presupuestoDAO.guardarTransferencia(monto);
	}
	
	
	private Date getFechaHoy(){
		Date date = new Date();
		return date;						
	}

	@SuppressWarnings("unchecked")
	public static <T> List<T> safeList(final List<T> other) {
		return other == null ? Collections.EMPTY_LIST : other;
	}
}