package com.ejie.ab63.control;

import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.Locale;

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.beans.factory.annotation.Qualifier;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.ModelAndView;

import com.ejie.ab63.util.exception.NegocioException;
import com.ejie.x38.util.StackTraceManager;

/**
 * Clase que se encarga de capturar las excepciones de la aplicación.
 *
 *  
 */
@ControllerAdvice()
public class GlobalExceptionHandler {

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

	@Autowired()
	@Qualifier(value = "messageSource")
	private ReloadableResourceBundleMessageSource messageSource;

	/**
	 * Captura las excepciones SQL.
	 *
	 * @param request
	 *            Request
	 * @param response
	 *            Response
	 * @param ex
	 *            Excepción
	 *
	 * @return vista
	 *
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	@ExceptionHandler(value = { SQLException.class, DataAccessException.class })
	public String handleSQLException(HttpServletRequest request, HttpServletResponse response, Exception ex)
			throws IOException {
		logger.error("SQLException Occured:: URL= {}", request.getRequestURL());
		logger.error(StackTraceManager.getStackTrace(ex));

		String ajaxHeader = request.getHeader("X-Requested-With");

		if ("XMLHttpRequest".equals(ajaxHeader)) {
			logger.info("Llamada Ajax detectada");

			String mensajeError = this.messageSource.getMessage("error.baseDatos", null,
					LocaleContextHolder.getLocale());

			// Ñapa Weblogic para evitar error: ProtocolException: Didn't meet
			// stated Content-Length
			response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			response.setContentType("text/html;charset=UTF-8");
			response.getWriter().write(mensajeError);
			response.flushBuffer();
			return null;
			// /////

		}
		return "database_error";
	}

	/**
	 * Captura las excepciones de entrada-salida
	 */
	@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "IOException occured")
	@ExceptionHandler(value = IOException.class)
	public void handleIOException(IOException ex) {
		logger.error("IOException handler executed");
		// returning 404 error code
	}

	/**
	 * Captura todas las excepciones y las redirige a la página de error.
	 *
	 * @param request
	 *            Request
	 * @param response
	 *            Response
	 * @param ex
	 *            Excepción
	 *
	 * @return Modelo y vista
	 *
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	@ExceptionHandler(value = Exception.class)
	public ModelAndView handleException(HttpServletRequest request, HttpServletResponse response, Exception ex)
			throws IOException {
		logger.error("Requested URL = {}", request.getRequestURL());
		logger.error("Exception Raised = {}", ex);
		logger.error(StackTraceManager.getStackTrace(ex));

		String ajaxHeader = request.getHeader("X-Requested-With");

		if ("XMLHttpRequest".equals(ajaxHeader)) {
			logger.info("Llamada Ajax detectada");

			String mensajeError = this.messageSource.getMessage("error.desconocido", null,
					LocaleContextHolder.getLocale());

			// Ñapa Weblogic para evitar error: ProtocolException: Didn't meet
			// stated Content-Length
			response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			response.setContentType("text/html;charset=UTF-8");
			response.getWriter().write(mensajeError);
			response.flushBuffer();
			return null;
			// /////

		}

		ModelAndView modelAndView = new ModelAndView();
		modelAndView.addObject("exception", ex);
		modelAndView.addObject("url", request.getRequestURL());

		modelAndView.setViewName("error");
		return modelAndView;
	}

	/**
	 * Captura todas las excepciones de validación y las redirige a la página
	 * de error controlado.
	 *
	 * @param request
	 *            Request
	 * @param response
	 *            Response
	 * @param ex
	 *            Excepción
	 *
	 * @return Modelo y vista
	 *
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	@ExceptionHandler(value = MaxUploadSizeExceededException.class)
	public ModelAndView handleMaxUploadSizeExceededException(HttpServletRequest request, HttpServletResponse response,
			Exception ex) throws IOException {
		logger.error("Requested URL = {}", request.getRequestURL());
		logger.error("Exception Raised = {}", ex);
		logger.error(StackTraceManager.getStackTrace(ex));

		String ajaxHeader = request.getHeader("X-Requested-With");

		if ("XMLHttpRequest".equals(ajaxHeader)) {
			logger.info("Llamada Ajax detectada");

			String mensajeError = this.messageSource.getMessage("error.maxFileSizeError", null,
					LocaleContextHolder.getLocale());

			// Ñapa Weblogic para evitar error: ProtocolException: Didn't meet
			// stated Content-Length
			response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			response.setContentType("text/html;charset=UTF-8");
			response.getWriter().write(mensajeError);
			response.flushBuffer();
			return null;
			// /////

		}

		ModelAndView modelAndView = new ModelAndView();
		modelAndView.addObject("exception", ex);
		modelAndView.addObject("url", request.getRequestURL());

		modelAndView.setViewName("errorControlado");
		return modelAndView;
	}

	/**
	 * Captura de errores de negocio en una petición Ajax.
	 *
	 * @param request
	 *            Request
	 * @param response
	 *            Response
	 * @param excepcion
	 *            Excepción
	 *
	 * @throws IOException
	 *             IOException
	 */
	@ExceptionHandler(value = NegocioException.class)
	public void handleNegocioException(HttpServletRequest request, HttpServletResponse response,
			NegocioException excepcion) throws IOException {

		String mensajeError = this.messageSource.getMessage(excepcion.getMessage(), null,
				LocaleContextHolder.getLocale());

		logger.warn("Exception Raised = {}", mensajeError);

		// Ñapa Weblogic para evitar error: ProtocolException: Didn't meet
		// stated Content-Length
		response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
		response.setContentType("text/html;charset=UTF-8");
		response.getWriter().write(mensajeError);
		response.flushBuffer();
		// /////
	}

	/**
	 * Captura las excepciones de validación hechas con la anotación @Valid
	 *
	 * @param response
	 *            Response
	 * @param ex
	 *            Excepción
	 *
	 * @throws IOException
	 *             IOException
	 *
	 */
	@ExceptionHandler(value = MethodArgumentNotValidException.class)
	public void processValidationError(HttpServletResponse response, MethodArgumentNotValidException ex)
			throws IOException {
		logger.debug("Handling form validation error");

		BindingResult result = ex.getBindingResult();
		List<FieldError> fieldErrors = result.getFieldErrors();

		// Ñapa Weblogic para evitar error: ProtocolException: Didn't meet
		// stated Content-Length
		response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
		response.setContentType("text/html;charset=UTF-8");
		response.getWriter().write(processFieldErrors(fieldErrors));
		response.flushBuffer();
		// /////
	}

	/**
	 * Procesa los campos con errores de validación y genera el mensaje de
	 * error a mostrar.
	 *
	 * @param fieldErrors
	 *            Campos con errores
	 *
	 * @return mensaje final de error localizado
	 */
	private String processFieldErrors(List<FieldError> fieldErrors) {
		StringBuilder mensajeError = new StringBuilder();

		for (FieldError fieldError : fieldErrors) {
			String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
			logger.debug("Adding error message: {} to field: {}", localizedErrorMessage, fieldError.getField());
			mensajeError.append(localizedErrorMessage);
			mensajeError.append("<br/>");
		}

		return mensajeError.toString();
	}

	/**
	 * Localiza el mensaje de error.
	 *
	 * @param fieldError
	 *            Campo con error
	 *
	 * @return mensaje de error localizado
	 */
	private String resolveLocalizedErrorMessage(FieldError fieldError) {
		Locale currentLocale = LocaleContextHolder.getLocale();
		String localizedErrorMessage = this.messageSource.getMessage(fieldError, currentLocale);

		// If a message was not found, return the most accurate field error code
		// instead.
		// You can remove this check if you prefer to get the default error
		// message.
		if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) {
			String[] fieldErrorCodes = fieldError.getCodes();
			localizedErrorMessage = fieldErrorCodes[0];
		}

		return localizedErrorMessage;
	}
}
