package com.qmth.themis.exam.api;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.gson.Gson;
import com.qmth.themis.business.annotation.ApiJsonObject;
import com.qmth.themis.business.annotation.ApiJsonProperty;
import com.qmth.themis.business.bean.exam.ExamActivityUnFinishBean;
import com.qmth.themis.business.bean.exam.ExamUnFinishBean;
import com.qmth.themis.business.cache.ExamActivityRecordCacheUtil;
import com.qmth.themis.business.cache.ExamRecordCacheUtil;
import com.qmth.themis.business.cache.ExamingDataCacheUtil;
import com.qmth.themis.business.cache.bean.*;
import com.qmth.themis.business.config.SystemConfig;
import com.qmth.themis.business.constant.SystemConstant;
import com.qmth.themis.business.dto.AuthDto;
import com.qmth.themis.business.dto.MqDto;
import com.qmth.themis.business.dto.cache.TEStudentCacheDto;
import com.qmth.themis.business.dto.response.TEExamActivityDto;
import com.qmth.themis.business.dto.response.TEExamDto;
import com.qmth.themis.business.dto.response.TEExamResultDto;
import com.qmth.themis.business.entity.TBSession;
import com.qmth.themis.business.entity.TEConfig;
import com.qmth.themis.business.entity.TEStudent;
import com.qmth.themis.business.enums.*;
import com.qmth.themis.business.service.*;
import com.qmth.themis.business.util.RedisUtil;
import com.qmth.themis.business.util.ServletUtil;
import com.qmth.themis.business.util.SessionUtil;
import com.qmth.themis.common.contanst.Constants;
import com.qmth.themis.common.enums.ExceptionResultEnum;
import com.qmth.themis.common.enums.Platform;
import com.qmth.themis.common.enums.Source;
import com.qmth.themis.common.exception.BusinessException;
import com.qmth.themis.common.signature.SignatureInfo;
import com.qmth.themis.common.signature.SignatureType;
import com.qmth.themis.common.util.AesUtil;
import com.qmth.themis.common.util.Result;
import com.qmth.themis.common.util.ResultUtil;
import com.qmth.themis.exam.config.ExamConstant;
import io.swagger.annotations.*;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.io.File;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * @Description: 考生 前端控制器
 * @Param:
 * @return:
 * @Author: wangliang
 * @Date: 2020/6/25
 */
@Api(tags = "考生Controller")
@RestController
@RequestMapping("/${prefix.url.exam}/student")
public class TEStudentController {

    @Resource
    TEStudentService teStudentService;

    @Resource
    CacheService cacheService;

    @Resource
    RedisUtil redisUtil;

    @Resource
    MqDtoService mqDtoService;

    @Resource
    TEConfigService teConfigService;

    @Resource
    TEExamService teExamService;

    @Resource
    TEExamStudentService teExamStudentService;

    @Resource
    TEExamActivityService teExamActivityService;

    @Resource
    TEExamCourseService teExamCourseService;

    @Resource
    SystemConfig systemConfig;

    @Resource
    TOeExamRecordService tOeExamRecordService;

    @ApiOperation(value = "学生登录接口")
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "学生信息", response = TEExamResultDto.class)})
    public Result login(@ApiJsonObject(name = "loginStudent", value = {
            @ApiJsonProperty(key = "identity", description = "证件号"),
            @ApiJsonProperty(key = "password", description = "密码"),
            @ApiJsonProperty(key = "orgId", type = "long", example = "1", description = "机构id")
    }) @ApiParam(value = "学生信息", required = true) @RequestBody Map<String, Object> mapParameter) throws NoSuchAlgorithmException {
        if (Objects.isNull(mapParameter)) {
            throw new BusinessException(ExceptionResultEnum.STUDENT_IS_NULL);
        }
        if (Objects.isNull(mapParameter.get("identity")) || Objects.equals(mapParameter.get("identity"), "")) {
            throw new BusinessException(ExceptionResultEnum.LOGIN_NAME_IS_NULL);
        }
        if (Objects.isNull(mapParameter.get("password")) || Objects.equals(mapParameter.get("password"), "")) {
            throw new BusinessException(ExceptionResultEnum.PASSWORD_IS_NULL);
        }
        Long orgId = null;
        if (Objects.nonNull(mapParameter.get("orgId"))) {
            orgId = Long.parseLong(String.valueOf(mapParameter.get("orgId")));
        }
        Long examId = null;
        if (Objects.nonNull(mapParameter.get("examId"))) {
            examId = Long.parseLong(String.valueOf(mapParameter.get("examId")));
        }
        if (Objects.isNull(orgId) && Objects.isNull(examId)) {
            throw new BusinessException("机构id或考试id必须有一个");
        }
        String identity = String.valueOf(mapParameter.get("identity"));
        String password = String.valueOf(mapParameter.get("password"));

        QueryWrapper<TEStudent> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(TEStudent::getIdentity, identity);
        TEStudent user = teStudentService.getOne(wrapper);
        //学生不存在
        if (Objects.isNull(user)) {
            throw new BusinessException(ExceptionResultEnum.STUDENT_NO);
        }
        if (StringUtils.isNotBlank(user.getBasePhotoPath())) {
            user.setBasePhotoPath(systemConfig.getProperty("aliyun.oss.url") + File.separator + user.getBasePhotoPath());
        }
        String loginPassword = AesUtil.decryptCs7(password, Constants.AES_RULE);
        //密码错误
        String aesPassword = AesUtil.decryptCs7(user.getPassword(), Constants.AES_RULE);
        if (!Objects.equals(loginPassword, aesPassword)) {
            throw new BusinessException(ExceptionResultEnum.PASSWORD_ERROR);
        }
        return userLoginCommon(user, examId, orgId);
    }

    /**
     * 学生登录公用
     *
     * @param teStudent
     * @param examId
     * @param orgId
     * @return
     * @throws NoSuchAlgorithmException
     */
    public Result userLoginCommon(TEStudent teStudent, Long examId, Long orgId) throws NoSuchAlgorithmException {
        //停用
        if (teStudent.getEnable().intValue() == 0) {
            throw new BusinessException(ExceptionResultEnum.STUDENT_ENABLE);
        }
        Platform platform = Platform.valueOf(ServletUtil.getRequestPlatform());
        String deviceId = ServletUtil.getRequestDeviceId();
        //添加用户鉴权缓存
        AuthDto authDto = cacheService.addStudentCache(teStudent.getId());
        //生成token
        String token = RandomStringUtils.randomAlphanumeric(32);
        TEStudentCacheDto teStudentCacheDto = (TEStudentCacheDto) redisUtil.getStudent(teStudent.getId());
        if (Objects.isNull(teStudentCacheDto)) {
            Gson gson = new Gson();
            teStudentCacheDto = gson.fromJson(gson.toJson(teStudent), TEStudentCacheDto.class);
        }
        //添加用户缓存
        redisUtil.setStudent(teStudent.getId(), teStudentCacheDto);
        String source = null;
        if (Objects.equals(platform.name(), Platform.WIN.name()) || Objects.equals(platform.name(), Platform.MAC.name())) {
            source = platform.getSource().split(",")[1];
        } else if (Objects.equals(platform.name(), Platform.WXAPP.name()) || Objects.equals(platform.name(), Platform.WEB.name())) {
            source = platform.getSource();
        } else if (Objects.equals(platform.name(), Platform.IOS.name()) || Objects.equals(platform.name(), Platform.ANDROID.name())) {
            source = platform.getSource().split(",")[2];
        }
        //添加用户会话缓存
        String sessionId = SessionUtil.digest(teStudent.getIdentity(), Math.abs(authDto.getRoleCodes().toString().hashCode()), source);

        Map<String, Object> expireMap = SystemConstant.getExpireTime(platform);
        Date expire = (Date) expireMap.get("date");
        Long redisExpire = Long.parseLong(String.valueOf(expireMap.get("redisExpire")));
        TBSession tbSession = new TBSession(sessionId, String.valueOf(teStudent.getId()), authDto.getRoleCodes().toString(), source, platform.name(), deviceId, ServletUtil.getRequest().getLocalAddr(), token, expire.getTime());
        redisUtil.setUserSession(sessionId, tbSession, redisExpire);
        //mq发送消息start
        MqDto mqDto = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), platform.name(), tbSession, MqTagEnum.valueOf(platform.name()), tbSession.getId(), teStudent.getIdentity());
        mqDtoService.assembleSendOneWayMsg(mqDto);
        MqDto mqDtoLog = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.STUDENT.name(), SystemOperationEnum.LOGIN, MqTagEnum.STUDENT, String.valueOf(teStudent.getId()), teStudent.getIdentity());
        mqDtoService.assembleSendOneWayMsg(mqDtoLog);
        //mq发送消息end
        //测试
        String test = SignatureInfo.build(SignatureType.TOKEN, sessionId, token);
        Map<String, Object> map = new HashMap<>();
        //获取未完考试
        if (Objects.isNull(ExamingDataCacheUtil.getUnFinishedRecordId(teStudent.getId()))) {
            //获取待考列表
            List<TEExamDto> list = teExamService.getWaitingExam(teStudent.getId(), examId, orgId);
            if (Objects.nonNull(list) && list.size() > 0) {
                map.put("waiting", list);
            }
        } else {
            Long recordId = ExamingDataCacheUtil.getUnFinishedRecordId(teStudent.getId());
            //获取考试记录缓存
            ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(recordId);
            if (Objects.isNull(status)) {
                throw new BusinessException("考试状态不能为空");
            }
            Long ecExamId = ExamRecordCacheUtil.getExamId(recordId);
            Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
            Long examActivityId = ExamRecordCacheUtil.getExamActivityId(recordId);
            Integer durationSeconds = Objects.isNull(ExamRecordCacheUtil.getDurationSeconds(recordId)) ? 0 : ExamRecordCacheUtil.getDurationSeconds(recordId);
            ExamCacheBean ec = teExamService.getExamCacheBean(ecExamId);//考试缓存
            Integer breakExpireSeconds = Objects.isNull(ec.getBreakExpireSeconds()) ? 0 : ec.getBreakExpireSeconds();
            ExamActivityCacheBean examActivityCacheBean = teExamActivityService.getExamActivityCacheBean(examActivityId);//考试场次缓存
            ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);

            //获取断点时间
            Long lastBreakTime = ExamRecordCacheUtil.getLastBreakTime(recordId);
            //获取剩余断点次数
            Integer alreadyBreakCount = Objects.isNull(ExamRecordCacheUtil.getAlreadyBreakCount(recordId)) ? 0 : ExamRecordCacheUtil.getAlreadyBreakCount(recordId);
            Integer leftBreakResumeCount = ec.getBreakResumeCount() - alreadyBreakCount;
            //如果断点时间大于整体断点时间，则强制交卷
            if (Objects.equals(status, ExamRecordStatusEnum.ANSWERING) || Objects.equals(status, ExamRecordStatusEnum.BREAK_OFF) || Objects.equals(status, ExamRecordStatusEnum.RESUME_PREPARE)) {
                if (Objects.nonNull(lastBreakTime) && (System.currentTimeMillis() - lastBreakTime) / 1000 > breakExpireSeconds) {
                    teExamService.finish(teStudentCacheDto.getId(), recordId, FinishTypeEnum.AUTO.name(), durationSeconds);
                    List<TEExamDto> list = teExamService.getWaitingExam(teStudent.getId(), examId, orgId);
                    if (Objects.nonNull(list) && list.size() > 0) {
                        map.put("waiting", list);
                    }
                } else {
                    //如果断点次数小于0，也强制交卷
                    if (leftBreakResumeCount < 0) {
                        teExamService.finish(teStudentCacheDto.getId(), recordId, FinishTypeEnum.AUTO.name(), durationSeconds);
                        List<TEExamDto> list = teExamService.getWaitingExam(teStudent.getId(), examId, orgId);
                        if (Objects.nonNull(list) && list.size() > 0) {
                            map.put("waiting", list);
                        }
                    } else {
                        //只有ANSWERING状态才生成断点
                        if (Objects.equals(status, ExamRecordStatusEnum.ANSWERING)) {
                            alreadyBreakCount++;
                            Long breakId = Constants.idGen.next();
                            ExamRecordCacheUtil.setLastBreakId(recordId, breakId, false);
                            ExamRecordCacheUtil.setStatus(recordId, ExamRecordStatusEnum.BREAK_OFF, false);
                            Long lastBreakTimeNow = System.currentTimeMillis();
                            ExamRecordCacheUtil.setLastBreakTime(recordId, lastBreakTimeNow, false);
                            ExamRecordCacheUtil.setAlreadyBreakCount(recordId, alreadyBreakCount, false);
                            Long lastStartTime = System.currentTimeMillis();
                            ExamRecordCacheUtil.setLastStartTime(recordId, lastStartTime, false);
                            String[] columns = new String[]{ExamRecordFieldEnum.last_break_id.name(), ExamRecordFieldEnum.status.name(), ExamRecordFieldEnum.last_break_time.name(), ExamRecordFieldEnum.already_break_count.name(), ExamRecordFieldEnum.last_start_time.name()};
                            Object[] values = new Object[]{breakId, ExamRecordStatusEnum.BREAK_OFF, lastBreakTimeNow, alreadyBreakCount, lastStartTime};
                            tOeExamRecordService.dataUpdatesMq(recordId, columns, values);
                            ExamConstant.sendExamStopMsg(recordId, true);
                            //考试断点异常原因 发送mq start
                            MqDto mqDtoBreak = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.EXAM_BREAK.name(), ExceptionEnum.NET_TIME_OUT, MqTagEnum.EXAM_BREAK, String.valueOf(recordId), String.valueOf(recordId));
                            mqDtoService.assembleSendOneWayMsg(mqDtoBreak);
                            //考试断点异常原因 发送mq end
                            //更新场次-考试记录缓存
                            ExamActivityRecordCacheUtil.setExamRecordStatus(examActivityId, recordId, new ExamActivityRecordCacheBean(ExamRecordCacheUtil.getExamStudentId(recordId), ExamRecordCacheUtil.getStatus(recordId)));
                        }
                        ExamUnFinishBean examUnFinishBean = this.unFinishCommon(recordId, ec, examStudentCacheBean, examActivityCacheBean, examStudentId);
                        map.put("unFinished", examUnFinishBean);
                    }
                }
            } else {
                List<TEExamDto> list = teExamService.getWaitingExam(teStudent.getId(), examId, orgId);
                if (Objects.nonNull(list) && list.size() > 0) {
                    map.put("waiting", list);
                }
            }
        }
        //获取全局考试配置
        TEConfig teConfig = teConfigService.getGlobalConfig();
//        map.put(SystemConstant.ACCESS_TOKEN, token);
        map.put(SystemConstant.GLOBAL, teConfig);
        map.put(SystemConstant.ACCESS_TOKEN, test);
        map.put(SystemConstant.STUDENT_ACCOUNT, teStudent);
        map.put(SystemConstant.SESSION_ID, sessionId);
        return ResultUtil.ok(map);
    }

    @ApiOperation(value = "登出接口")
    @RequestMapping(value = "/logout", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class)})
    public Result logout() throws NoSuchAlgorithmException {
        TEStudentCacheDto teStudent = (TEStudentCacheDto) ServletUtil.getRequestStudentAccount();
        TBSession tbSession = (TBSession) ServletUtil.getRequestSession();
        if (Objects.isNull(tbSession)) {
            throw new BusinessException(ExceptionResultEnum.LOGIN_NO);
        }
        AuthDto authDto = (AuthDto) redisUtil.get(SystemConstant.studentOauth + "::" + teStudent.getId());
        redisUtil.deleteUserSession(tbSession.getId());
        //循环检查该用户下其他平台是否存在session,不存在则删除用户缓存和鉴权缓存
        boolean delete = true;
        for (Source s : Source.values()) {
            String sessionId = SessionUtil.digest(teStudent.getIdentity(), Math.abs(authDto.getRoleCodes().toString().hashCode()), s.name());
            if (Objects.nonNull(redisUtil.getUserSession(sessionId))) {
                delete = false;
                break;
            }
        }
        if (delete) {
            redisUtil.deleteStudent(teStudent.getId());
            cacheService.removeStudentCache(teStudent.getId());
        }
        //mq发送消息start
        MqDto mqDto = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.STUDENT.name(), SystemOperationEnum.LOGOUT, MqTagEnum.STUDENT, String.valueOf(teStudent.getId()), teStudent.getIdentity());
        mqDtoService.assembleSendOneWayMsg(mqDto);
        //mq发送消息end
        return ResultUtil.ok(Collections.singletonMap("success", true));
    }

    /**
     * 未完成公用
     *
     * @param recordId
     * @param ec
     * @param examStudentCacheBean
     * @param examActivityCacheBean
     * @param examStudentId
     * @return
     */
    public ExamUnFinishBean unFinishCommon(Long recordId, ExamCacheBean ec, ExamStudentCacheBean examStudentCacheBean, ExamActivityCacheBean examActivityCacheBean, Long examStudentId) {
        ExamCourseCacheBean examCourseCacheBean = teExamCourseService.getExamCourseCacheBean(ec.getId(), examStudentCacheBean.getCourseCode());
        TEExamActivityDto teExamActivityDto = new TEExamActivityDto(ec, examActivityCacheBean, examStudentCacheBean, examStudentId, examCourseCacheBean);
        Gson gson = new Gson();
        ExamActivityUnFinishBean examActivityUnFinishBean = gson.fromJson(gson.toJson(teExamActivityDto), ExamActivityUnFinishBean.class);
        examActivityUnFinishBean.setRecordId(recordId);
        ExamRecordCacheUtil.setStartTime(recordId, examActivityUnFinishBean.getStartTime(), false);
        ExamRecordCacheUtil.setEndTime(recordId, examActivityCacheBean.getFinishTime(), false);
        ExamRecordCacheUtil.setOpeningSeconds(recordId, examActivityCacheBean.getOpeningSeconds(), false);
        ExamRecordCacheUtil.setMinDurationSeconds(recordId, ec.getMinDurationSeconds(), false);
        ExamRecordCacheUtil.setMaxDurationSeconds(recordId, examActivityCacheBean.getMaxDurationSeconds(), false);
        ExamRecordCacheUtil.setForceFinish(recordId, ec.getForceFinish(), false);
        ExamRecordCacheUtil.setExamFinalFinishTime(recordId, null);
        String[] columns = new String[]{ExamRecordFieldEnum.start_time.name(), ExamRecordFieldEnum.end_time.name(), ExamRecordFieldEnum.opening_seconds.name(), ExamRecordFieldEnum.min_duration_seconds.name(), ExamRecordFieldEnum.max_duration_seconds.name(), ExamRecordFieldEnum.force_finish.name()};
        Object[] values = new Object[]{examActivityUnFinishBean.getStartTime(), examActivityCacheBean.getFinishTime(), examActivityCacheBean.getOpeningSeconds(), ec.getMinDurationSeconds(), examActivityCacheBean.getMaxDurationSeconds(), ec.getForceFinish()};
        tOeExamRecordService.dataUpdatesMq(recordId, columns, values);
        return new ExamUnFinishBean(ec.getId(), ec.getName(), ec.getPreNotice(), ec.getPreNoticeStaySeconds(), ec.getPostNotice(), examActivityUnFinishBean);
    }
}
