package com.yvan.mvc;

import com.google.common.base.Strings;
import com.yvan.ModelOps;
import com.yvan.platform.JsonWapper;
import com.yvan.platform.YvanUtil;
import com.yvan.springmvc.HttpUtils;
import com.yvan.springmvc.JsonView;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.context.annotation.Bean;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 异常控制器，springmvc发生异常页面后会跳转到本控制器
 * Created by luoyifan on 2017/3/1.
 */
@RestController
@ControllerAdvice
@Slf4j
public class ErrorController {

    @RequestMapping("/error/404")
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Object error404() {
        return ModelOps.newFail("NotFound");
    }

    public static ModelOps PROCESS_ERROR(HttpServletRequest request, Exception ex) {
        Map<String, Object> r = new LinkedHashMap<>();
        String msg;
        Enumeration<String> enuma = request.getHeaderNames();
        Map<String, Object> vl = new LinkedHashMap<>();
        while (enuma.hasMoreElements()) {
            String key = enuma.nextElement();
            vl.put(key, request.getHeader(key));
        }

        r.put("Headers", YvanUtil.toJsonPretty(vl));
        r.put("ServerName", request.getServerName());
        r.put("Host", request.getHeader("Host"));
        r.put("RemoteIP", HttpUtils.currentRemoteIp());
        r.put("url", request.getScheme() + "://" + request.getHeader("Host") + request.getRequestURI()
                + ((!Strings.isNullOrEmpty(request.getQueryString()) ? "?" + request.getQueryString() : "")));

        HttpUtils.currentResponse().setStatus(500);

        if (ex instanceof NoHandlerFoundException) {
            msg = "ResourceNotFound";
            HttpUtils.currentResponse().setStatus(404);

        } else if (ex instanceof MethodArgumentNotValidException) {
            //验证失败
            msg = "ValidateError";
            BindingResult bindingResult = ((MethodArgumentNotValidException) ex).getBindingResult();
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                r.put(fieldError.getObjectName(), fieldError.getDefaultMessage());
            }

        } else if (ex instanceof ValidateErrorException) {
            msg = "ValidateError";
            r.put(((ValidateErrorException) ex).getPropertyName(), ((ValidateErrorException) ex).getPropertyDesc() + " " + ex.getMessage());

        } else if (ex instanceof DuplicateKeyException) {
            msg = "DuplicateKeyException";
            log.error(request.getRequestURI(), ex);

        } else if (ex instanceof HttpMessageNotReadableException) {
            //json
            msg = "JsonConvertFailure";
            log.error(request.getRequestURI(), ex);

        } else if (ex instanceof UnauthorizedException) {
            //未授权
            msg = "Unauthorized";
            log.error(request.getRequestURI() + " 401!");
            HttpUtils.currentResponse().setStatus(401);

        } else if (ex instanceof UnauthenticatedException) {
            //未登录
            msg = "Forbidden";
            HttpUtils.currentResponse().setStatus(403);

        } else {
            //其他异常
            log.error(ex.toString(), ex);
            msg = "InternalServerError";

            StringWriter sw = new StringWriter();
            try (PrintWriter pw = new PrintWriter(sw)) {
                ex.printStackTrace(pw);
                r.put("errorTrace", sw.toString());
            }
        }

        r.put("errorMsg", msg);
        return ModelOps.newFail(msg, r);
    }

    @SneakyThrows
    @ExceptionHandler(Exception.class)
    public ModelAndView handleError(HttpServletRequest request, Exception ex) {
        val uri = request.getRequestURI();
        val r = PROCESS_ERROR(request, ex);
        if (uri.endsWith(".html") || uri.endsWith(".htm")) {
            return new ModelAndView("/error/500.html.ftl", new JsonWapper(r).getInnerMap());
        }
        return new ModelAndView(new JsonView(r));
    }

    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer() {

        return (ConfigurableEmbeddedServletContainer container) -> {

            container.addErrorPages(
                    new ErrorPage(HttpStatus.NOT_FOUND, "/error/404")
            );
        };
    }
}
