package me.qwq.doghouse.component;

import lombok.extern.slf4j.Slf4j;
import me.qwq.doghouse.exception.PageNotFoundException;
import me.qwq.doghouse.exception.RException;
import me.qwq.doghouse.pojo.dto.LayPageResp;
import me.qwq.doghouse.pojo.dto.R;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.stream.Collectors;

import javax.security.auth.login.LoginException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
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.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import com.fasterxml.jackson.databind.JsonNode;

/**
 * 异常处理
 */
@ControllerAdvice
@Slf4j
public class DoghouseExceptionHandler {

    @Autowired
    HttpServletResponse response;
    
    @Autowired
    HttpServletRequest request;
    
    @ExceptionHandler({
        BindException.class,
        MethodArgumentNotValidException.class})
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public <Ex extends BindException> R<?> handleBindException(Ex ex) {
        StringBuilder message = new StringBuilder();
        ex.getBindingResult().getAllErrors()
                .forEach(error -> message.append(error.getDefaultMessage()).append("\n"));
        log.warn("Resolved Exception {}", message.substring(0, message.length() - 1));
        log.warn(httpServletRequestToString(request));
        return bodyOrPage(HttpStatus.BAD_REQUEST, ex);
    }
    
    @ExceptionHandler(LoginException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
    public R<?> handleLoginException(LoginException ex) {
        return bodyOrPage(HttpStatus.NOT_ACCEPTABLE, ex);
    }
    
    @ExceptionHandler(RException.class)
    @ResponseBody
    public R<?> handleRException(RException ex) {
        response.setStatus(ex.getHttpStatus().value());
        if (ex.getLogRequest()) {
            log.warn(httpServletRequestToString(request));
        }
        return bodyOrPage(ex.getHttpStatus(), ex);
    }
    
    @ExceptionHandler(ServletException.class)
    @ResponseBody
    public R<?> handleServletException(ServletException ex) {
        HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
        if (ex instanceof HttpRequestMethodNotSupportedException)
            httpStatus = HttpStatus.METHOD_NOT_ALLOWED;
        response.setStatus(httpStatus.value());
        return bodyOrPage(httpStatus, ex);
    }

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public R<?> handleException(Exception ex) {
        if (ex instanceof PageNotFoundException) {
            throw (PageNotFoundException)ex;
        }
        log.warn("Resolved exception {}", ex.getMessage());
        log.warn(httpServletRequestToString(request));
        return bodyOrPage(HttpStatus.INTERNAL_SERVER_ERROR, ex);
    }
    
    private R<?> bodyOrPage(HttpStatus httpStatus, Exception ex) {
        boolean isPage = (ex instanceof RException || ex instanceof LoginException) ?
                false : isPage();
        if (isPage) {
            if (ex instanceof NoResourceFoundException nrfe) {
                if (StringUtils.isNotEmpty(nrfe.getMessage())
                        && nrfe.getMessage().endsWith(" .well-known/appspecific/com.chrome.devtools.json.")) {
                    // 傻逼 Chrome 开发工具默认调用该地址
                    return null;
                }
            }
            throw ex == null ? 
                    new RuntimeException("Page exception raised") : 
                        new RuntimeException(ex);
        }
        ex.printStackTrace();
        return ex != null ? R.status(httpStatus, ex.getMessage()) : R.status(httpStatus);
    }
    /**
     * 根据 @Autowired request 等自动判断当前请求是 text/html 还是 application/json
     * @return
     */
    private boolean isPage() {
        boolean isPage = true;
        String accept;
        // 匹配当前 request 在控制层的方法，根据是否是
        // @ResponseBody 注解、@RestController 注解、
        // 已知的返回类型来判断返回的是否是页面
        Object handler = request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
        
        if (handler instanceof HandlerMethod handlerMethod) {
            Method method = handlerMethod.getMethod();
            Class<?> controllerClass = handlerMethod.getBeanType();
            
            boolean hasResponseBody =
                    method.isAnnotationPresent(ResponseBody.class) ||
                    controllerClass.isAnnotationPresent(ResponseBody.class) ||
                    controllerClass.isAnnotationPresent(RestController.class);
            
            Class<?> returnType = method.getReturnType();
            boolean isDtoReturnType = 
                    returnType.equals(R.class) ||
                    returnType.equals(LayPageResp.class) ||
                    JsonNode.class.isAssignableFrom(returnType);
            
            if (hasResponseBody || isDtoReturnType) {
                isPage = false;
            }
        }
        if (isPage && (accept = request.getHeader("Accept")) != null) {
            int indexOfHtml = accept.indexOf("text/html"), indexOfJson = accept.indexOf("application/json");
            if (indexOfHtml == -1 && indexOfJson != -1) {
                isPage = false;
            } else if (indexOfHtml != -1 && indexOfJson != -1) {
                isPage = indexOfHtml < indexOfJson;
            }
        }
        return isPage;
    }
    
    private static String httpServletRequestToString(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();

        sb.append("Request Method = \"" + request.getMethod() + "\", ");
        sb.append("Request URL Path = \"" + request.getRequestURL() + "\", ");

        String headers =
            Collections.list(request.getHeaderNames()).stream()
                .map(headerName -> headerName + " : " + Collections.list(request.getHeaders(headerName)) )
                .collect(Collectors.joining(", "));

        if (headers.isEmpty()) {
            sb.append("Request headers: NONE,");
        } else {
            sb.append("Request headers: ["+headers+"],");
        }

        String parameters =
            Collections.list(request.getParameterNames()).stream()
                .map(p -> {
                    String[] values = request.getParameterValues(p);
                    return p + ": [" + StringUtils.join(values, ", ") + "]";
                })
                .collect(Collectors.joining(", "));

        if (parameters.isEmpty()) {
            sb.append("Request parameters: NONE.");
        } else {
            sb.append("Request parameters: [" + parameters + "].");
        }

        return sb.toString();
    }
}
