package com.ejie.ab39a.exception;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

import com.ejie.ab39a.exceptions.ErrorField;
import com.ejie.ab39a.exceptions.ErrorGenericoException;
import com.ejie.ab39a.utils.Utilidades;
import com.ejie.x38.util.IframeXHREmulationUtils;

/**
 * Manejador de excepciones
 * 
 *  
 * 
 */
public class Ab39aExceptionHandler {

	private static final Logger LOGGER = LoggerFactory.getLogger(Ab39aExceptionHandler.class);

	/*
	 * Tiles de error
	 */
	private static final String TILES_ERROR = "error";

	/*
	 * Literales de error
	 */
	private static final String ERROR_APLIC = "error.aplicacion";

	/*
	 * Parametro de la respuesta de Ajax
	 */
	private static final String RESPUESTA_AJAX = "X-Requested-With";

	@Autowired
	private ReloadableResourceBundleMessageSource messageSource;

	/**
	 * Codigos de error de BBDD oracle.<br/>
	 * Deben agregarse con el siguiente formato:
	 * <p>
	 * "ORA_" + errorCode
	 * </p>
	 * 
	 *  
	 */
	private static enum SQL_CODES {
		ORA_2292("error.integridadDatos.eliminarReg"), //
		ORA_1("error.integridadDatos.claveDuplicada") //
		;
		/**
		 * 
		 * @param literal
		 *            String
		 */
		private SQL_CODES(String literal) {
			this.literal = literal;
		}

		private String literal;

		/**
		 * Recupera un codigo asegurandonos de que no lanza excepcion si no lo
		 * encuentra
		 * 
		 * @param value
		 *            String value
		 * @return SQL_CODES SQL_CODES
		 */
		public static SQL_CODES get(String value) {
			try {
				return Ab39aExceptionHandler.SQL_CODES.valueOf(value);
			} catch (IllegalArgumentException e) {
				return null;
			}
		}

		/**
		 * 
		 * @return String
		 */
		public String getLiteral() {
			return this.literal;
		}
	}

	/**
	 * Manejador de excepciones controladas del tipo
	 * 'DataIntegrityViolationException'.<br/>
	 * En la aplicacion, estas excepciones ocurren cuando se intenta eliminar un
	 * registro que es FK de otro, o tambien por DuplicateKeyException (cuando
	 * se intenta insertar un registro con una PK ya existente).
	 * 
	 * @param exception
	 *            DataIntegrityViolationException exception
	 * @param request
	 *            HttpServletRequest request
	 * @param response
	 *            HttpServletResponse response
	 * @return String String
	 * @throws IOException
	 *             e e
	 */
	@ExceptionHandler(value = { DataIntegrityViolationException.class })
	public ModelAndView handleAccessIntegridadException(DataIntegrityViolationException exception,
			HttpServletRequest request, HttpServletResponse response) throws IOException {
		ModelAndView modelAndView = null;
		ErrorField errorField = null;
		if (exception.getCause() instanceof SQLException) {
			/*
			 * Si la excepcion es de este tipo, cargamos el mensaje en funcion
			 * del codigo de error que nos devuelve oracle.
			 */
			SQL_CODES sql_code = Ab39aExceptionHandler.SQL_CODES.get(Utilidades.obtenerCadena("ORA_",
					String.valueOf(((SQLException) exception.getCause()).getErrorCode())));
			errorField = new ErrorField(sql_code != null ? sql_code.getLiteral() : "error.integridadDatos.general");
		} else {
			errorField = new ErrorField("error.integridadDatos.general");
		}
		if (this.isAjaxRequest(request)) {
			Utilidades.sendError(ErroresViewHelper.obtenerErrorRup(this.messageSource, Arrays.asList(errorField)),
					response);
		} else if (IframeXHREmulationUtils.isIframeEmulationRequired(request)) {
			ErroresViewHelper.writeRdoIframe(request, response, false,
					ErroresViewHelper.obtenerErrorRup(this.messageSource, Arrays.asList(errorField)));
		} else {
			request.setAttribute("errorsMessages", Arrays.asList(errorField));
			modelAndView = new ModelAndView();
			modelAndView.setViewName(Ab39aExceptionHandler.TILES_ERROR);
		}
		return modelAndView;

	}

	/**
	 * Manejador de excepciones controladas del tipo 'ErrorGenericoException'.
	 * 
	 * @param exception
	 *            ErrorGenericoException exception
	 * @param request
	 *            HttpServletRequest request
	 * @param response
	 *            HttpServletResponse response
	 * @return ModelAndView ModelAndView
	 * @throws IOException
	 *             IOException IOException
	 */
	@ExceptionHandler(value = ErrorGenericoException.class)
	public ModelAndView handleErrorGenericoException(ErrorGenericoException exception, HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		ModelAndView modelAndView = null;
		if (this.isAjaxRequest(request)) {
			Utilidades.sendError(ErroresViewHelper.obtenerErrorRup(this.messageSource, exception.getErrorsFields()),
					response);
		} else if (IframeXHREmulationUtils.isIframeEmulationRequired(request)) {
			ErroresViewHelper.writeRdoIframe(request, response, exception.isGeneraTextArea(),
					ErroresViewHelper.obtenerErrorRup(this.messageSource, exception.getErrorsFields()));
		} else {
			request.setAttribute("errorsMessages", exception.getErrorsFields());
			modelAndView = new ModelAndView();
			this.loadExceptionModel(exception, modelAndView);
			if (Utilidades.isEmpty(exception.getTiles())) {
				modelAndView.setViewName(Ab39aExceptionHandler.TILES_ERROR);
			} else {
				modelAndView.setViewName(exception.getTiles());
			}
		}
		return modelAndView;

	}

	/**
	 * Gestiona el resto de excepciones
	 * 
	 * @param exception
	 *            Exception exception
	 * @param request
	 *            HttpServletRequest request
	 * @param response
	 *            HttpServletResponse response
	 * @return ModelAndView ModelAndView
	 * @throws IOException
	 *             e e
	 */
	@ExceptionHandler
	public ModelAndView handleGeneral(Exception exception, HttpServletRequest request, HttpServletResponse response)
			throws IOException {
		Ab39aExceptionHandler.LOGGER.debug("Error no controlado", exception);
		ModelAndView modelAndView = null;
		List<ErrorField> errorFields = new ArrayList<ErrorField>();
		errorFields.add(new ErrorField(Ab39aExceptionHandler.ERROR_APLIC));
		// Llamada Ajax
		if (this.isAjaxRequest(request)) {
			response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE,
					ErroresViewHelper.obtenerErrorRup(this.messageSource, errorFields));
			return modelAndView;
		} else if (IframeXHREmulationUtils.isIframeEmulationRequired(request)) {
			ErroresViewHelper.writeRdoIframe(request, response, false,
					ErroresViewHelper.obtenerErrorRup(this.messageSource, errorFields));
		} else {
			modelAndView = new ModelAndView();
			modelAndView.setViewName(Ab39aExceptionHandler.TILES_ERROR);
			this.loadExceptionModel(exception, modelAndView);
		}
		return modelAndView;
	}

	/**
	 * handle Validation
	 * 
	 * @param exception
	 *            BindException exception
	 * @param request
	 *            HttpServletRequest request
	 * @param response
	 *            HttpServletResponse response
	 * @return ModelAndView ModelAndView
	 */
	@ExceptionHandler(value = { org.springframework.validation.BindException.class })
	public ModelAndView handleValidation(BindException exception, HttpServletRequest request,
			HttpServletResponse response) {

		// ModelAndView modelAndView = null;
		return null;
	}

	/**
	 * Carga los datos de la excepcion en la vista de error
	 * 
	 * @param exception
	 *            Exception
	 * @param modelAndView
	 *            ModelAndView
	 */
	private void loadExceptionModel(Exception exception, ModelAndView modelAndView) {
		modelAndView.addObject("exception_name", exception.getClass().getName());
		modelAndView.addObject("exception_message", exception.getMessage());
		StringBuilder sbTrace = new StringBuilder();
		for (StackTraceElement trace : exception.getStackTrace()) {
			sbTrace.append(trace.toString()).append("</br>");
		}
		modelAndView.addObject("exception_trace", sbTrace);
	}

	/**
	 * Comprueba si es una peticion ajax. Tiene en cuenta si es una descarga de
	 * fichero mediante la utilidad ajaxDownload de Ab12aUtils.
	 * 
	 * @param request
	 *            HttpServletRequest
	 * @return Boolean
	 */
	private Boolean isAjaxRequest(HttpServletRequest request) {
		// Se comprueba el parametro 'ajaxDownloadFile' que lo informa la
		// funcion de Ab12aUtils
		String ajaxDownloadFile = request.getParameter("ajaxDownloadFile");
		Boolean isAjaxDownloadFile = Boolean
				.valueOf((ajaxDownloadFile != null ? ajaxDownloadFile.toLowerCase() : "false"));
		return (request.getHeaders(Ab39aExceptionHandler.RESPUESTA_AJAX).hasMoreElements() || isAjaxDownloadFile);
	}

}
