package xyz.wbsite.dbtool.web.action;

import org.apache.tomcat.util.http.fileupload.FileUtils;
import xyz.wbsite.dbtool.web.config.ActionConfig;
import xyz.wbsite.dbtool.web.frame.base.*;
import xyz.wbsite.dbtool.web.frame.auth.LocalData;
import xyz.wbsite.dbtool.web.frame.utils.FileUtil;
import xyz.wbsite.dbtool.web.frame.utils.MapperUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 全局请求Controller，如果无特殊请求，则不需再增加其他Controller
 * 全局htm后缀入口{@link GlobalController#action(Model, HttpServletRequest, HttpServletResponse)}
 * 全局异常捕捉{@link GlobalController#exceptionHandler(HttpServletRequest, HttpServletResponse, Model, Exception)}
 * 全局上传接口{@link GlobalController#upload(HttpServletRequest)}
 * 全局下载接口{@link GlobalController#download(String)}
 * 全局消息订阅{@link GlobalController#sse(String)}
 * <p>
 * 说明Request命名规则，驼峰式命名
 * Api#Example#Request ==> 目标#动作#Request
 *
 * @author author
 * @version 0.0.1
 * @since 2019-06-16
 */
@Controller
@ControllerAdvice
public class GlobalController implements ErrorController {

    @Value("${web.welcome.page}")
    private String homePage;
    @Value("${web.login.page}")
    private String loginPage;
    @Autowired
    private FreeMarkerViewResolver viewResolver;

    /**
     * 全局异常捕捉
     *
     * @param request
     * @param response
     * @param exception 要捕获的异常
     * @return
     */
    @ExceptionHandler(Exception.class)
    public String exceptionHandler(HttpServletRequest request, HttpServletResponse response, Model model, Exception exception) {
        StringBuffer msg = new StringBuffer("");
        if (exception != null) {
            msg = new StringBuffer("");
            String message = exception.toString();
            int length = exception.getStackTrace().length;
            if (length > 0) {
                msg.append("<a>").append(message).append("</a><br>");
                for (int i = 0; i < length; i++) {
                    msg.append("<a>").append(exception.getStackTrace()[i]).append("</a><br>");
                }
            } else {
                msg.append(message);
            }
        }
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        model.addAttribute("msg", msg.toString());
        return "500";
    }

    private final static String ERROR_PATH = "/error";

    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }

    @RequestMapping(value = ERROR_PATH)
    public String error(HttpServletRequest request, HttpServletResponse response) {
        switch (response.getStatus()) {
            case 404:
                return "404";
            case 403:
                try {
                    response.sendRedirect("/login.htm");
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return "403";
            case 500:
                return "500";
            default:
                return "403";
        }
    }

    @RequestMapping("/")
    public String home() {
        Token token = LocalData.getToken();
        if (token == null) {
            return "redirect:" + loginPage;
        } else {
            return "redirect:" + homePage;
        }
    }

    /**
     * 当未明确指定控制器时，走该请求，默认返回对应的layout布局和screen视图
     * 当需要使用layout时，不需要返回值，ViewNameTranslator会处理对应关系
     *
     * @param model
     * @param request
     */
    @RequestMapping({"/**/*.htm"})
    public String action(Model model, HttpServletRequest request, HttpServletResponse response) {
        String servletPath = request.getServletPath();// /**/*.htm
        String layout = "/layout/default";
        String action = LocalData.getAction();// **/*

        Pattern compile = Pattern.compile("^/(.+)\\.htm");
        Matcher matcher = compile.matcher(servletPath);
        if (matcher.find()) {
            action = matcher.group(1);
            LocalData.setAction(action);
        }

        try {
            LocaleResolver localeResolver = (LocaleResolver) request.getAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE);
            Locale locale = localeResolver.resolveLocale(request);

            {//查询screen
                String[] split = action.split("/");
                StringBuilder sb = new StringBuilder("");
                sb.append("screen");
                for (int i = 0; i < split.length; i++) {
                    sb.append(File.separator);
                    sb.append(split[i]);
                }
                layout = sb.toString();
                View view = viewResolver.resolveViewName(layout, locale);
                if (view == null) {
                    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                    return error(request, response);
                }

                // 尝试执行Screen执行器(服务器渲染)，并返回视图模板
                try {
                    String beanClassName = (ActionConfig.SCREEN_PREFIX + action).toLowerCase();
                    Screen screenExec = LocalData.getApplicationContext().getBean(beanClassName, Screen.class);
                    screenExec.exec(model, request, response);

                    if (response.getStatus() != HttpServletResponse.SC_OK) {
                        return error(request, response);
                    }
                } catch (BeansException e) {

                }
            }

            {//查找layout
                String[] split = action.split("/");

                int lt = split.length;
                while (lt > 0) {

                    StringBuilder sb = new StringBuilder("");
                    sb.append("layout");
                    for (int i = 0; i < lt - 1; i++) {
                        sb.append(File.separator);
                        sb.append(split[i]);
                    }

                    layout = sb.toString() + File.separator + split[split.length - 1];

                    View view = viewResolver.resolveViewName(layout, locale);
                    //无法找到对应layout，使用默认layout
                    if (view == null) {
                        layout = sb.toString() + File.separator + "default";
                        View defaultView = viewResolver.resolveViewName(layout, locale);
                        if (null == defaultView && lt == 1) {
                            System.err.println("can not find layout/default.ftl");
                        } else if (null == defaultView) {
                            lt--;
                        } else {
                            break;
                        }
                    } else {
                        break;
                    }
                }
            }
        } catch (Exception e) {
            return exceptionHandler(request, response, model, e);
        }

        //todo 可在此获取共性数据(也可以在全局拦截器GlobalHandlerInterceptor、拦截器作用域比此更高)，
        //todo 例如用户信息等。其他业务数据在页面渲染后通过Ajax请求
        return layout;
    }

    @RequestMapping("/upload")
    @ResponseBody
    public BaseResponse upload(HttpServletRequest request) {
        FileUploadResponse fileUploadResponse = new FileUploadResponse();
        MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
        MultipartFile target = multipartHttpServletRequest.getFile("file");

        String fileName = target.getOriginalFilename();

        //========
        //处理文件
        //========
        fileUploadResponse.setId(1L);
        fileUploadResponse.setUrl("example.com\\img\\1.jpg");
        fileUploadResponse.setDownloadUrl("example.com\\img\\1.jpg");


        if (target != null) {
            fileUploadResponse.addError(ErrorType.BUSINESS_ERROR, "文件上传成功,但未处理文件[" + fileName + "]!");
        } else {
            fileUploadResponse.addError(ErrorType.BUSINESS_ERROR, "文件上传失败!");
        }

        return fileUploadResponse;
    }


    @RequestMapping("/ajax/{module}/{target}/{method}")
    @ResponseBody
    public BaseResponse ajax(@PathVariable String module, @PathVariable String target, @PathVariable String method, @RequestBody String param) {
        BaseResponse baseResponse = null;

        try {
            String beanClassName = (ActionConfig.AJAX_PREFIX + module + "/" + target).toLowerCase();
            Object ajax = LocalData.getApplicationContext().getBean(beanClassName);

            Class ajaxClass = ajax.getClass();
            Method methodC = ajaxClass.getMethod(method, String.class);
            Object invoke = methodC.invoke(ajax, param);
            if (invoke instanceof BaseResponse) {
                baseResponse = (BaseResponse) invoke;
            } else {
                baseResponse = new BaseResponse();
                baseResponse.addError(ErrorType.BUSINESS_ERROR, "方法返回值错误!");
            }
        } catch (BeansException e) {
            baseResponse = new BaseResponse();
            baseResponse.addError(ErrorType.BUSINESS_ERROR, "未找到对应的目标!");
        } catch (NoSuchMethodException e) {
            baseResponse = new BaseResponse();
            baseResponse.addError(ErrorType.BUSINESS_ERROR, "未找到对应的方法!");
        } catch (IllegalAccessException e) {
            baseResponse = new BaseResponse();
            baseResponse.addError(ErrorType.BUSINESS_ERROR, "方法执必须公开!");
        } catch (InvocationTargetException e) {
            baseResponse = new BaseResponse();
            baseResponse.addError(ErrorType.BUSINESS_ERROR, "方法执行错误!");
        }
        return baseResponse;
    }

    @RequestMapping("/download")
    @ResponseBody
    public ResponseEntity<byte[]> download(@RequestParam(value = "file", required = false) String file) throws IOException {

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);

        //========
        //下载DEMO
        //========
        if (file == null) {
            file = "test.txt";
            headers.setContentDispositionFormData("attachment", new String(file.getBytes("UTF-8"), "iso-8859-1"));
            return new ResponseEntity<byte[]>("test".getBytes(),
                headers, HttpStatus.CREATED);
        }

        return new ResponseEntity<byte[]>(FileUtil.readFileToByteArray(new File(file)),
                headers, HttpStatus.CREATED);
    }


    private static ConcurrentHashMap<String, SseEmitter> sseMap = new ConcurrentHashMap();

    /**
     * Sse推送服务，服务器向js推送自定义消息
     * Sse容器{@link GlobalController#sseMap}
     * Sse批量推送{@link GlobalController#pushAll}
     */
    @RequestMapping(value = "/sse/{userId}", produces = "text/event-stream;charset=UTF-8")
    public SseEmitter sse(@PathVariable String userId) {
        SseEmitter sseEmitter = new SseEmitter(10000000L);
        if (sseMap.get(userId) != null) {
            sseMap.remove(userId);
        }
        sseMap.put(userId, sseEmitter);
        return sseEmitter;
    }

    /**
     * Sse批量推送
     *
     * @param data 推送对象
     */
    public static void pushAll(Object data) {
        for (String s : sseMap.keySet()) {
            try {
                sseMap.get(s).send(MapperUtil.toJson(data), MediaType.APPLICATION_JSON);
            } catch (IOException e) {
                sseMap.remove(s);
            }
        }
    }
}
