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

import java.sql.SQLException;
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.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.executor.result.ResultMapException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
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.transaccion.exceptions.dtos.TransaccionException;
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) throws ResultMapException, SQLException, TransaccionException {
		try {
			presupuestoDAO.almacenaMonto(anio, monto);
			return "APLICADO";
		} catch (PersistenceException e) {
			throw new TransaccionException("Error al cargar los datos de actualizacion " + e);
		} catch (DataAccessException e) {
			throw new TransaccionException("Error al cargar los datos de actualizacion " + e);
		}
	}

	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 ResultMapException, SQLException, TransaccionException {
		String mensaje = "";

		Boolean valido = true;

		try {
			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);

					}

				}

			}
		} catch (PersistenceException e) {
			throw new TransaccionException("Error al cargar los datos de actualizacion " + e);
		} catch (DataAccessException e) {
			throw new TransaccionException("Error al cargar los datos de actualizacion " + e);
		}

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

	public void actualizarApartadosTmp(List<PresupuestoApartadoDTO> apartados, Integer anio)
			throws ResultMapException, SQLException, TransaccionException {
		try {
			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);

			}
		} catch (PersistenceException e) {
			throw new TransaccionException("Error al cargar los datos de actualizacion " + e);
		} catch (DataAccessException e) {
			throw new TransaccionException("Error al cargar los datos de actualizacion " + e);
		}

	}

	public List<PresupuestoDTO> obtenerPresptoXTipo(String tipo)
			throws ResultMapException, SQLException, TransaccionException {
		List<PresupuestoDTO> presupuestos = new ArrayList<PresupuestoDTO>();
		List<PresupuestoApartadoDTO> apartados = null;
		List<ApartadoPreBD> asignado = null;
		List<ApartadoPreBD> comprometido = null;
		try {
			apartados = presupuestoDAO.getApartadosXTipo(tipo);
			asignado = presupuestoDAO.obtenerPresupuestoAsignado(apartados);
			comprometido = presupuestoDAO.obtenerPresupuestoComprometido(tipo);
		} catch (PersistenceException e) {
			throw new TransaccionException("Error al cargar los datos de actualizacion " + e);
		} catch (DataAccessException e) {
			throw new TransaccionException("Error al cargar los datos de actualizacion " + e);
		}

		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)
			throws ResultMapException, SQLException, TransaccionException {
		PresupuestoDTO presupuesto = new PresupuestoDTO();
		List<PresupuestoApartadoDTO> apartados = null;
		List<ApartadoPreBD> asignado = null;
		List<ApartadoPreBD> comprometido = null;

		try {
			apartados = presupuestoDAO.getApartadosXTipo(tipo);
			asignado = presupuestoDAO.obtenerApartadosAsignado(apartados, anio);
			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));
			}
		} catch (PersistenceException e) {
			throw new TransaccionException("Error al cargar los datos de actualizacion " + e);
		} catch (DataAccessException e) {
			throw new TransaccionException("Error al cargar los datos de actualizacion " + e);
		}

		return presupuesto;
	}

	public String redistribuirApartados(List<PresupuestoApartadoDTO> apartados, Integer anio) throws ResultMapException, SQLException, TransaccionException {
		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 ResultMapException, SQLException, TransaccionException {
		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 ResultMapException, SQLException, TransaccionException {
		MontoTransferirDTO monto = new MontoTransferirDTO(apOrigen, apDestino, montoOrign, montoDestino,
				montoReasignado, new Date());
		try {
			presupuestoDAO.guardarTransferencia(monto);
		} catch (PersistenceException e) {
			throw new TransaccionException("Error al cargar los datos de actualizacion " + e);
		} catch (DataAccessException e) {
			throw new TransaccionException("Error al cargar los datos de actualizacion " + e);
		}
	}

	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;
	}
}