package com.style24.front.support.controller;

import java.sql.SQLException;
import java.sql.SQLRecoverableException;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.ibatis.binding.BindingException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.core.env.Environment;
import org.springframework.mobile.device.Device;
import org.springframework.mobile.device.DeviceUtils;
import org.springframework.mobile.device.site.SitePreference;
import org.springframework.mobile.device.site.SitePreferenceUtils;
import org.springframework.mobile.device.util.ResolverUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.Assert;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.fasterxml.jackson.databind.JsonMappingException;
import com.style24.core.support.message.TscMessageByLocale;
import com.style24.front.support.env.TsfConstants;
import com.style24.front.support.security.session.TsfSession;
import com.style24.persistence.domain.Login;

import lombok.extern.slf4j.Slf4j;

import com.gagaframework.web.rest.exception.GagaRestException;
import com.gagaframework.web.rest.server.GagaResponse;
import com.gagaframework.web.rest.server.GagaResponseStatus;
import com.gagaframework.web.util.GagaCookieUtil;

/**
 * Controller Advice
 * 
 * @author gagamel
 * @since 2020. 9. 11
 */
@ControllerAdvice
@Slf4j
public class TsfBaseController {

	@Autowired
	private Environment env;

	@Autowired
	private TscMessageByLocale message;

	/**
	 * Json data 형식 외의 모든 문자열의 앞, 뒤 공백 제거
	 * Json data 형식의 모든 문자열의 앞, 뒤 공백 제거 시는 AdmStringTrim에서 처리 (@RequestBody annotation 이용 시)
	 * @param binder
	 */
	@InitBinder
	public void initBinder(WebDataBinder binder) {
		StringTrimmerEditor stringtrimmer = new StringTrimmerEditor(true);
//		stringtrimmer.getAsText().replaceAll("<", "&lt;").replaceAll(">", "&gt;");
		binder.registerCustomEditor(String.class, stringtrimmer);
	}

	@ModelAttribute("env")
	public Environment getEnvironment() {
		return env;
	}

	/**
	 * Get 세션 정보
	 *
	 * 1.View 단에서 사용법
	 * 		th:value="${sessionInfo.userId}"
	 * 2.Java 단에서 사용법
	 * 		super.getSession().getUserId()
	 * @return
	 */
	@ModelAttribute("sessionInfo")
	public Login getSession() {
		return TsfSession.getInfo();
	}

	/**
	 * Get 프론트구분(P:PC, M:모바일)
	 *
	 * 1.View 단에서 사용법
	 * 		th:value="${frontGb}"
	 * 2.Java 단에서 사용법
	 * 		super.getFrontGb()
	 * @return
	 */
	@ModelAttribute("frontGb")
	public String getFrontGb() {
		return TsfSession.getFrontGb();
	}

	@ModelAttribute("snsLoginPrefix")
	public String getSnsLoginPrefix() {
		return TsfConstants.SNSLOGIN_PREFIX;
	}

	@ModelAttribute("osType")
	public String getOsType() {
		return TsfSession.getAttribute("osType");
	}

	/**
	 * APP인지 아닌지 (true/false)
	 * @return
	 */
	@ModelAttribute("isApp")
	public String isApp() {
		return TsfSession.getAttribute("isApp");
	}

	/**
	 * APP명
	 * @return
	 */
	@ModelAttribute("appName")
	public String getAppName() {
		return TsfSession.getAttribute("appName");
	}

	/**
	 * APP버전
	 * @return
	 */
	@ModelAttribute("appVersion")
	public String getAppVersion() {
		return TsfSession.getAttribute("appVersion");
	}

	/**
	 * APP의 디바이스토큰
	 * @return
	 */
	@ModelAttribute("deviceToken")
	public String getDeviceToken() {
		return TsfSession.getAttribute("deviceToken");
	}

	@ResponseBody
	public GagaResponse ok() {
		return GagaResponse.of(GagaResponseStatus.SUCCESS.getCode(), "SUCCESS");
	}

	@ResponseBody
	public GagaResponse ok(String message) {
		return GagaResponse.of(GagaResponseStatus.SUCCESS.getCode(), message);
	}

	@ResponseBody
	public GagaResponse error(String message) {
		return GagaResponse.error(GagaResponseStatus.FAIL.getCode(), message);
	}

	@ExceptionHandler(AccessDeniedException.class)
	@ResponseBody
	public GagaResponse handleForbidden(Exception e) {
		return GagaResponse.error(GagaResponseStatus.FORBIDDEN.getCode(), e.getMessage());
	}

	@ExceptionHandler(TypeMismatchException.class)
	@ResponseBody
	public GagaResponse handleBadRequestException(Exception e) {
		errorLogging(e);
		return GagaResponse.error(GagaResponseStatus.BAD_REQUEST.getCode(), e.getMessage());
	}

	@ExceptionHandler(MissingServletRequestParameterException.class)
	@ResponseBody
	public GagaResponse handleRequestParameterException(MissingServletRequestParameterException e) {
		errorLogging(e);
		return GagaResponse.error(GagaResponseStatus.BAD_REQUEST.getCode(), e.getMessage());
	}

	/**
	 * HttpRequestMethodNotSupportedException Handler
	 * @param e - HttpRequestMethodNotSupportedException, SQLRecoverableException
	 * @return forwarding URI
	 */
	@ExceptionHandler({HttpRequestMethodNotSupportedException.class, SQLRecoverableException.class})
	public String handleException(HttpRequestMethodNotSupportedException e) {
		return "forward:/error/500";
	}

	@ExceptionHandler(Throwable.class)
	@ResponseBody
	public GagaResponse handleException(Throwable throwable) {
		errorLogging(throwable);

		Throwable rootCause = ExceptionUtils.getRootCause(throwable);

		if (rootCause != null) {
			throwable = rootCause;
		}

		if (throwable instanceof SQLException || throwable instanceof BindingException ||
			throwable instanceof JsonMappingException) {
			String message = String.format("데이터 처리중 에러가 발생하였습니다. 시스템 관리자에게 문의하세요.");
			return GagaResponse.error(GagaResponseStatus.INTERNAL_SERVER_ERROR.getCode(), message);
		}

		return GagaResponse.error(GagaResponseStatus.INTERNAL_SERVER_ERROR.getCode(), throwable.getMessage());
	}

	private void errorLogging(Throwable throwable) {
		if (log.isErrorEnabled()) {
			Throwable rootCause = ExceptionUtils.getRootCause(throwable);

			if (rootCause != null) {
				throwable = rootCause;
			}

			if (throwable.getMessage() != null) {
				log.error(throwable.getMessage(), throwable);
			} else {
				log.error("ERROR", throwable);
			}
		}
	}

	@ExceptionHandler(MethodArgumentNotValidException.class)
	@ResponseBody
	public Object processValidationError(MethodArgumentNotValidException ex) {
		FieldError fieldError = ex.getBindingResult().getFieldErrors().get(0);
		GagaResponse error = GagaResponse.error(GagaResponseStatus.INTERNAL_SERVER_ERROR.getCode(), fieldError.getDefaultMessage());
		error.getError().setRequiredKey(fieldError.getField());
		return error;
	}

	/**
	 * Custom Validation using group class
	 * @param domain - Domain to validate
	 * @param groupClass - Group class to validate
	 */
	public void customValidation(Object clazz, Class<?> groupClass) {
		Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

		Set<ConstraintViolation<Object>> violations = validator.validate(clazz, groupClass);

		for (ConstraintViolation<?> violation : violations) {
			String validationMessage = violation.getMessage();
			validationMessage = StringUtils.removeStart(validationMessage, "{");
			validationMessage = StringUtils.removeEnd(validationMessage, "}");
			throw new GagaRestException(GagaResponseStatus.FAIL.getCode(), message.getMessage(validationMessage));
		}
	}

	public String getDeviceViewName(String viewName) {
		RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
		Assert.isInstanceOf(ServletRequestAttributes.class, attrs);
		HttpServletRequest request = ((ServletRequestAttributes)attrs).getRequest();

		Device device = DeviceUtils.getCurrentDevice(request);
		SitePreference sitePreference = SitePreferenceUtils.getCurrentSitePreference(request);
		String resolvedViewName = viewName;

		if (ResolverUtils.isNormal(device, sitePreference)) {
			resolvedViewName = "web/" + viewName + "Web";
		} else if (ResolverUtils.isMobile(device, sitePreference)) {
			if (GagaCookieUtil.getCookie(request, TsfConstants.CK_PREFIX + "_site_preference").equals("normal")) {
				resolvedViewName = "web/" + viewName + "Web";
			} else {
				resolvedViewName = "mob/" + viewName + "Mob";
			}
		} else if (ResolverUtils.isTablet(device, sitePreference)) {
			if (GagaCookieUtil.getCookie(request, TsfConstants.CK_PREFIX + "_site_preference").equals("normal")) {
				resolvedViewName = "web/" + viewName + "Web";
			} else {
				resolvedViewName = "mob/" + viewName + "Mob";
			}
		}

		log.debug("resolvedViewName: {}", resolvedViewName);

		return stripTrailingSlash(resolvedViewName);
	}

	private String stripTrailingSlash(String viewName) {
		if (viewName.endsWith("//")) {
			return viewName.substring(0, viewName.length() - 1);
		}

		return viewName;
	}

}
