package cn.com.qmth.stmms.ms.log.aop;

import cn.com.qmth.stmms.ms.commons.config.LevelConfig;
import cn.com.qmth.stmms.ms.commons.constant.ArbitrateCallback;
import cn.com.qmth.stmms.ms.commons.constant.ArbitrateResult;
import cn.com.qmth.stmms.ms.commons.utils.SqlUtil;
import cn.com.qmth.stmms.ms.core.cache.ParamCache;
import cn.com.qmth.stmms.ms.core.domain.*;
import cn.com.qmth.stmms.ms.core.domain.task.MarkTask;
import cn.com.qmth.stmms.ms.core.domain.user.MarkUser;
import cn.com.qmth.stmms.ms.core.domain.user.Role;
import cn.com.qmth.stmms.ms.core.repository.*;
import cn.com.qmth.stmms.ms.marking.config.MarkingConfig;
import cn.com.qmth.stmms.ms.marking.dto.MarkTaskDTO;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description: 纪检审查aop
 * @Param:
 * @return:
 * @Author: wangliang
 * @Date: 2019/10/19
 */
@Aspect
@Component
public class MarkLogAop {

    private static Logger LOGGER = LoggerFactory.getLogger(MarkLogAop.class);

    private final String TOKEN = "token",
            MARK_USER = "markUser",//登录用户
            PARAM_ARGS = "args",//方法参数
    //            PARAM_ARG_NAMES = "argNames",//方法参数名
    ACTION = "action",//动作
            SAMPLING = "sampling",//标准卷设置
            LEVELING = "leveling",//定档
            REJECT = "reject",//档位打回
            PAPER = "paper",//paper
            STAGE = "stage",//stage
            LEVEL = "level",//level
            BODY = "body",//body
            RESULT = "result",//result
            DEFAULT_RESULT = "-";//默认值

    @Autowired
    private MarkLogRepo markLogRepo;

    @Autowired
    MarkUserRepo markUserRepo;

    @Autowired
    private PaperRepo paperRepo;

    @Autowired
    SqlUtil sqlUtil;

    @Autowired
    LevelConfig levelConfig;

    @Autowired
    MarkTaskRepo markTaskRepo;

    @Autowired
    MarkingConfig markingConfig;

    @Autowired
    WorkRepo workRepo;

    @Autowired
    ArbitrateCallback arbitrateCallback;

    @Resource
    MarkTaskJobRepo markTaskJobRepo;

    /**
     * 标准卷、定档、档位打回切入点
     */
    @Pointcut("execution(* cn.com.qmth.stmms.ms.marking.api.PaperApi.marking(..))")
    public void paperAspect() {
    }

    /**
     * 分档、打分、回评档位、回评分数、档位打回回评切入点
     */
    @Pointcut("execution(* cn.com.qmth.stmms.ms.marking.api.MarkTaskApi.marking(..))")
    public void markAspect() {
    }

    /**
     * 采集端采集试卷切入点
     */
    @Pointcut("execution(* cn.com.qmth.stmms.ms.collect.api.CollectApi.imageUploadSheet(..))")
    public void collectAspect() {
    }

    /**
     * 标准卷设置、定档设置、档位打回切入
     *
     * @param joinPoint
     * @param result
     * @throws Exception
     */
    @AfterReturning(value = "paperAspect()", returning = RESULT)
    @Transactional
    public void afterReturnPaperPoint(JoinPoint joinPoint, Object result) throws Exception {
        Map<String, Object> map = getAspectCommon(joinPoint);
        JSONArray jsonArgsArray = (JSONArray) map.get(PARAM_ARGS);
        MarkUser markUser = (MarkUser) map.get(MARK_USER);
//        Paper paper = JSONObject.parseObject(((JSONObject) jsonArgsArray.get(0)).toJSONString(), Paper.class);
        JSONObject jsonObjectResult = (JSONObject) jsonArgsArray.get(0);
        String paperIds = (String) jsonObjectResult.get("paperIds");
        if (paperIds.length() > 0) {
            String[] ids = paperIds.split(",");
            for (String id : ids) {
                Paper paper = paperRepo.findOne(Long.valueOf(id));

                String action = null;
                Integer operType = null;
                String originLevel = null;
                if (Objects.nonNull(jsonObjectResult) && Objects.nonNull(jsonObjectResult.get(ACTION))) {
                    action = (String) jsonObjectResult.get(ACTION);
                    if (Objects.equals(SAMPLING, action)) {
                        //标准卷设置
                        operType = MarkLogOperType.CRITERION_PAPER_SET.getId();
                        paper.setOneClick(false);
                    } else if (Objects.equals(LEVELING, action)) {
                        //定档设置
                        operType = MarkLogOperType.ONE_CLICK_LEVEl.getId();
                        //原始档位
                        originLevel = Objects.nonNull(jsonObjectResult.get("originLevel")) ? (String) jsonObjectResult.get("originLevel") : null;
                        paper.setOneClick(true);
                    } else if (Objects.equals(REJECT, action)) {
                        //档位打回设置
                        operType = MarkLogOperType.CALLBACK_LEVEl.getId();
                        paper.setOneClick(false);
                    }
                }
                //标准卷设置、定档设置、档位打回start
                MarkLog markLogPrev = markLogRepo.findTopByCreateUserIdAndOperTypeAndSubjectAndExamNumberOrderByCreateTimeDesc(markUser.getId(), operType, markUser.getSubject().toString(), paper.getExamNumber());
                String operResult = DEFAULT_RESULT;
                if (Objects.nonNull(markLogPrev)) {
                    operResult = Optional.ofNullable(markLogPrev.getOperDataAfter()).orElse(DEFAULT_RESULT);
                }
                if (Objects.equals(LEVELING, action)) {
                    //定档设置
                    operResult = originLevel;
                }
                Work work = workRepo.findOne(paper.getWorkId());
                MarkLog markLog = new MarkLog(markUser.getId(), markUser.getName(), markUser.getRole(), markUser.getSubject(), paper.getExamNumber(), paper.getStudentName(), operType, paper.getWorkId(), paper.getId(), MarkStage.LEVEL, operResult, String.valueOf(jsonObjectResult.get(LEVEL)), null, work.getName());
                LOGGER.info("markLog:{}", JSONObject.toJSONString(markLog));
                //标准卷设置、定档设置、档位打回end
                markLogRepo.save(markLog);
                //更新
                paperRepo.save(paper);
            }
        }
    }

    /**
     * 分档、打分、回评档位、回评分数、档位打回回评切入
     *
     * @param joinPoint
     * @param result
     * @throws Exception
     */
    @AfterReturning(value = "markAspect()", returning = RESULT)
    @Transactional
    public void afterReturnMarkPoint(JoinPoint joinPoint, Object result) throws Exception {
        Map<String, Object> map = getAspectCommon(joinPoint);
        JSONArray jsonArgsArray = (JSONArray) map.get(PARAM_ARGS);
        MarkUser markUser = (MarkUser) map.get(MARK_USER);
        JSONObject aopResult = JSONObject.parseObject(JSONObject.toJSONString(result));
        boolean isRejected = false, oldRejected = false;
        if (Objects.nonNull(aopResult)) {
            MarkTaskDTO resultBody = JSONObject.parseObject(((JSONObject) aopResult.get(BODY)).toJSONString(), MarkTaskDTO.class);
            isRejected = resultBody.isRejected();//新的是否撤档
            oldRejected = resultBody.isOldRejected();//旧的是否撤档
        }
        JSONObject jsonObject = (JSONObject) jsonArgsArray.get(0);
        Paper paper = JSONObject.parseObject(((JSONObject) jsonObject.get(PAPER)).toJSONString(), Paper.class);
        JSONObject jsonObjectResult = (JSONObject) jsonArgsArray.get(1);
        LOGGER.info("jsonObject:{},paper:{},jsonObjectResult:{}", jsonObject, paper, jsonObjectResult);
        Integer operType = MarkLogOperType.LEVEL.getId();//分档,第一次为分档，之后为回评档位,档位打回后为档位打回回评
        //分档、打分、回评档位、回评分数、档位打回回评start
        MarkStage markStage = MarkStage.valueOf(String.valueOf(jsonObjectResult.get(STAGE)));
        if (markStage.ordinal() == 1 && paper.isMarkByLeader()) {//分档,如果已经有科组长定档，则不允许评卷员再分档
            return;
        } else if (markStage.ordinal() == 1 && oldRejected && !isRejected) {//档位打回回评
            operType = MarkLogOperType.CALLBACK_ONCE_LEVEL.getId();
        } else if (markStage.ordinal() == 2) {//打分
            operType = MarkLogOperType.SCORE.getId();
        }
        //先查是否有档位或分数打回
        String operResult = DEFAULT_RESULT;
        if (markStage.ordinal() == 1) {//分档
            //先查是否有档位打回回评的数据
            MarkLog markLogPrev = markLogRepo.findTopByCreateUserIdAndOperTypeAndSubjectAndExamNumberOrderByCreateTimeDesc(markUser.getId(), MarkLogOperType.CALLBACK_ONCE_LEVEL.getId(), markUser.getSubject().toString(), paper.getExamNumber());
            if (Objects.isNull(markLogPrev)) {//如果为null，则再查分档和回评档位
                if (oldRejected && !isRejected) {//档位打回
                    operType = MarkLogOperType.CALLBACK_ONCE_LEVEL.getId();
                } else {
                    operType = selectOperType(markUser.getId(), MarkLogOperType.LEVEL.getId(), MarkLogOperType.ONCE_LEVEl.getId(), markUser.getSubject().toString(), paper.getExamNumber());
                    markLogPrev = markLogRepo.findTopByCreateUserIdAndOperTypeAndSubjectAndExamNumberOrderByCreateTimeDesc(markUser.getId(), operType, markUser.getSubject().toString(), paper.getExamNumber());
                    if (Objects.nonNull(markLogPrev)) {
                        operResult = Optional.ofNullable(markLogPrev.getOperDataAfter()).orElse(DEFAULT_RESULT);
                    } else {
                        if (operType != MarkLogOperType.LEVEL.getId()) {
                            markLogPrev = markLogRepo.findTopByCreateUserIdAndOperTypeAndSubjectAndExamNumberOrderByCreateTimeDesc(markUser.getId(), MarkLogOperType.LEVEL.getId(), markUser.getSubject().toString(), paper.getExamNumber());
                            operResult = Optional.ofNullable(markLogPrev.getOperDataAfter()).orElse(DEFAULT_RESULT);
                        }
                    }
                }
            } else {
                operType = MarkLogOperType.CALLBACK_ONCE_LEVEL.getId();//档位打回
                operResult = Optional.ofNullable(markLogPrev.getOperDataAfter()).orElse(DEFAULT_RESULT);
            }
        } else if (markStage.ordinal() == 2) {//打分
            operType = selectOperType(markUser.getId(), MarkLogOperType.SCORE.getId(), MarkLogOperType.ONCE_SCORE.getId(), markUser.getSubject().toString(), paper.getExamNumber());
            MarkLog markLogPrev = markLogRepo.findTopByCreateUserIdAndOperTypeAndSubjectAndExamNumberOrderByCreateTimeDesc(markUser.getId(), operType, markUser.getSubject().toString(), paper.getExamNumber());
            if (Objects.nonNull(markLogPrev)) {
                operResult = Optional.ofNullable(markLogPrev.getOperDataAfter()).orElse(DEFAULT_RESULT);
            } else {
                if (operType != MarkLogOperType.SCORE.getId()) {
                    markLogPrev = markLogRepo.findTopByCreateUserIdAndOperTypeAndSubjectAndExamNumberOrderByCreateTimeDesc(markUser.getId(), MarkLogOperType.SCORE.getId(), markUser.getSubject().toString(), paper.getExamNumber());
                    operResult = Optional.ofNullable(markLogPrev.getOperDataAfter()).orElse(DEFAULT_RESULT);
                }
            }
        }
        Work work = workRepo.findOne(paper.getWorkId());
        MarkLog markLog = new MarkLog(markUser.getId(), markUser.getName(), markUser.getRole(), markUser.getSubject(), paper.getExamNumber(), paper.getStudentName(), operType, paper.getWorkId(), paper.getId(), markStage, operResult, String.valueOf(jsonObjectResult.get(RESULT)), null, work.getName());
        LOGGER.info("markLog:{}", JSONObject.toJSONString(markLog));
        //分档、打分、回评档位、回评分数、档位打回、档位打回回评end
        markLogRepo.save(markLog);
        //20191107wangliang加入仲裁自动打回算法 start
        if (markStage.ordinal() == 1 && paper.isArbitrated()) {
            if (ParamCache.paramMap.get(work.getId()).getAutoCallback() == 1) {//为true则自动打回
                List<MarkTask> markTasks = markTaskRepo.findByPaperIdAndStage(paper.getId(), markStage);
                JSONObject js = new JSONObject();
                js.put("paperId", paper.getId());
                js.put("type", "autoCallback");
                Integer deviation = Optional.ofNullable(ParamCache.paramMap.get(work.getId()).getDeviation()).orElse(3);
                js.put("deviation", deviation.intValue());
                MarkTaskJob markTaskJob = new MarkTaskJob(js.toJSONString(), false, paper.getWorkId());
                autoCallback(markTasks, deviation.intValue(), paper, markTaskJob, work.getName());
                markTaskJobRepo.save(markTaskJob);
            }
        }
        //20191107wangliang加入仲裁自动打回算法 end
    }

    /**
     * 自动打回异步回调
     *
     * @param markTasks
     * @param deviation
     * @param paper
     * @param markTaskJob
     * @param workName
     */
    public void autoCallback(List<MarkTask> markTasks, int deviation, Paper paper, MarkTaskJob markTaskJob, String workName) {
        List<MarkTask> result = markTasks.stream().filter(x -> Objects.isNull(x.getResult())).collect(Collectors.toList());
        if (Objects.nonNull(result) && result.size() > 0) {
            LOGGER.info("数据有误，未完成评卷任务");
            markTaskJobRepo.updateMarkTaskJobByVersion(markTaskJob.getId(), markTaskJob.getVersion());
            return;
        }
        Map<Long, String> levelsMap = markTasks.stream().collect(Collectors.toMap(MarkTask::getMarkerId, o -> o.getResult().toUpperCase()));
        LOGGER.info("this:{} markLogAop异步回调进来了", this);
        arbitrateCallback.judge(levelsMap, deviation, new ArbitrateResult() {
            @Override
            public void callback(List<ArbitrateCallback.Distance> list) {
                LOGGER.info("this:{} callback markLogAop异步回调进来了", this);
                String operResult = DEFAULT_RESULT;
                //全部打回则不触发自动仲裁
                if (Objects.nonNull(list) && list.size() > 0 && markTasks.size() != list.size()) {
                    paper.reject(null);
                    List<MarkLog> markLogList = new ArrayList<>();
                    for (ArbitrateCallback.Distance d : list) {
                        for (MarkTask m : markTasks) {
                            if (Objects.equals(String.valueOf(d.getC()), m.getResult()) && d.getMarkId().longValue() == m.getMarkerId().longValue()) {
                                m.setRejected(true);
                                m.setOriginLevel(m.getResult());
                                m.setResult(null);
                                MarkLog markLogPrev = markLogRepo.findTopByCreateUserIdAndOperTypeAndSubjectAndExamNumberOrderByCreateTimeDesc(m.getMarkerId(), MarkLogOperType.SYSTEM_CALLBACK_LEVEl.getId(), m.getSubject().toString(), paper.getExamNumber());
                                if (Objects.nonNull(markLogPrev)) {
                                    operResult = Optional.ofNullable(markLogPrev.getOperDataAfter()).orElse("-");
                                }
                                //加入打回日志
                                MarkLog markLog = new MarkLog(m.getMarkerId(), m.getMarkerName(), Role.MARKER, m.getSubject(), paper.getExamNumber(), paper.getStudentName(), MarkLogOperType.SYSTEM_CALLBACK_LEVEl.getId(), paper.getWorkId(), paper.getId(), m.getStage(), operResult, String.valueOf(d.getC()), MarkLogOperType.SYSTEM_CALLBACK_LEVEl.getName(), workName);
                                markLogList.add(markLog);
                            }
                        }
                    }
                    int count = markTaskJobRepo.updateMarkTaskJobByVersion(markTaskJob.getId(), markTaskJob.getVersion());
                    if (count > 0) {
                        markTaskRepo.save(markTasks);
                        paperRepo.save(paper);
                        markLogRepo.save(markLogList);
                    }
                }
                LOGGER.info("this:{} callback markLogAop异步回调结束了", this);
            }
        });
        LOGGER.info("this:{} markLogAop异步回调结束了", this);
    }

    /**
     * 查询处于哪个档位公用
     *
     * @param createUserId
     * @param operTypeStart
     * @param operTypeEnd
     * @param subject
     * @param examNumber
     * @return
     */
    public Integer selectOperType(Long createUserId, Integer operTypeStart, Integer operTypeEnd, String subject, String examNumber) {
        String sql = "select t.oper_type as operType, count(*) as result from mark_log as t where t.create_user_id = ? and (t.oper_type = ? or t.oper_type = ?) and t.subject = ? and t.exam_number = ? group by t.oper_type ";
        List list = sqlUtil.execSqlForMapNative(sql, createUserId, operTypeStart, operTypeEnd, subject, examNumber);
        Integer operType = operTypeStart;
        if (Objects.nonNull(list) && list.size() > 0) {
            for (int i = 0; i < list.size(); i++) {
                Map m = (Map) list.get(i);
                Integer type = Integer.parseInt(String.valueOf(m.get("operType")));
                if (type.intValue() == operTypeStart.intValue()) {
                    operType = operTypeEnd;
                    break;
                } else {
                    operType = type;
                    break;
                }
            }
        }
        return operType;
    }

    /**
     * 切面公用方法
     *
     * @param joinPoint
     * @return
     * @throws Exception
     */
    public Map getAspectCommon(JoinPoint joinPoint) throws Exception {
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        LOGGER.info("{} aspect path:{}", this.getClass().getMethods()[0], className + "." + methodName);
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        HttpSession session = request.getSession();
        String token = (String) session.getAttribute(TOKEN);
        if (Objects.isNull(token)) {
            throw new Exception("请先登录");
        }
        Object[] args = joinPoint.getArgs(); // 参数值
        MarkTask markTask = null;
        MarkUser markUser = null;
        if (Objects.nonNull(args) && args.length > 0) {
            for (Object o : args) {
                if (o instanceof MarkTask) {
                    markTask = (MarkTask) o;
                    break;
                }
            }
        }
        if (Objects.nonNull(markTask)) {
            markUser = markUserRepo.findOne(markTask.getMarkerId());
        } else {
            String[] strs = token.split("#");
            markUser = strs == null || strs[2] == null || strs[2] == "null" ? null : markUserRepo.findOne(Long.parseLong(strs[2]));
        }
        LOGGER.info("markUser:{}", JSONObject.toJSONString(markUser));
//        String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();//参数名
        JSONArray jsonArgsArray = JSONObject.parseArray(JSONObject.toJSONString(args));
//        JSONArray jsonArgNamesArray = JSONObject.parseArray(JSONObject.toJSONString(argNames));
        LOGGER.info("jsonArgsArray:{},jsonArgNamesArray:{}", jsonArgsArray);
//        LOGGER.info("jsonArgNamesArray:{}", jsonArgNamesArray);
        Map<String, Object> map = new HashMap<>();
        map.put(PARAM_ARGS, jsonArgsArray);
//        map.put(PARAM_ARG_NAMES, jsonArgNamesArray);
        map.put(MARK_USER, markUser);
        return map;
    }
}
