package com.qmth.themis.exam.api;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qmth.themis.business.annotation.ApiJsonObject;
import com.qmth.themis.business.annotation.ApiJsonProperty;
import com.qmth.themis.business.bean.StudentParams;
import com.qmth.themis.business.bean.exam.ExamActivityUnFinishBean;
import com.qmth.themis.business.bean.exam.ExamUnFinishBean;
import com.qmth.themis.business.cache.ExamRecordCacheUtil;
import com.qmth.themis.business.cache.ExamingDataCacheUtil;
import com.qmth.themis.business.cache.bean.ExamActivityCacheBean;
import com.qmth.themis.business.cache.bean.ExamCacheBean;
import com.qmth.themis.business.cache.bean.ExamCourseCacheBean;
import com.qmth.themis.business.cache.bean.ExamStudentCacheBean;
import com.qmth.themis.business.constant.SystemConstant;
import com.qmth.themis.business.dto.AuthDto;
import com.qmth.themis.business.dto.ExpireTimeDTO;
import com.qmth.themis.business.dto.MqDto;
import com.qmth.themis.business.dto.cache.TEStudentCacheDto;
import com.qmth.themis.business.dto.response.TEExamActivityWaitDto;
import com.qmth.themis.business.dto.response.TEExamResultDto;
import com.qmth.themis.business.dto.response.TEExamWaitDto;
import com.qmth.themis.business.entity.TBOrg;
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.ExamRecordStatusEnum;
import com.qmth.themis.business.enums.MqTagEnum;
import com.qmth.themis.business.enums.SystemOperationEnum;
import com.qmth.themis.business.enums.WebsocketStatusEnum;
import com.qmth.themis.business.service.*;
import com.qmth.themis.business.util.*;
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.util.GsonUtil;
import com.qmth.themis.common.util.IpUtil;
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.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @Description: 考生 前端控制器
 * @Param:
 * @return:
 * @Author: wangliang
 * @Date: 2020/6/25
 */
@Api(tags = "考生Controller")
@RestController
@RequestMapping(SystemConstant.PREFIX_URL_OE + "/student")
@Validated
public class TEStudentController {

    private final static Logger log = LoggerFactory.getLogger(TEStudentController.class);

    @Resource
    TEStudentService teStudentService;

    @Resource
    ThemisCacheService themisCacheService;

    @Resource
    RedisUtil redisUtil;

    @Resource
    MqDtoService mqDtoService;

    @Resource
    TEConfigService teConfigService;

    @Resource
    TEExamService teExamService;

    @Resource
    TEExamStudentService teExamStudentService;

    @Resource
    TEExamActivityService teExamActivityService;

    @Resource
    TEExamCourseService teExamCourseService;

    @Resource
    OssUtil ossUtil;

    @Resource
    TOeExamRecordService tOeExamRecordService;

    @Resource
    MqUtil mqUtil;

    @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"),
                    @ApiJsonProperty(key = "examId", 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(SystemConstant.ORG_ID))) {
            orgId = Long.parseLong(String.valueOf(mapParameter.get(SystemConstant.ORG_ID)));
        }
        Long examId = null;
        TBOrg tbOrg = null;
        if (Objects.nonNull(mapParameter.get(SystemConstant.EXAM_ID))) {
            examId = Long.parseLong(String.valueOf(mapParameter.get(SystemConstant.EXAM_ID)));
            ExamCacheBean ec = teExamService.getExamCacheBean(examId);
            tbOrg = themisCacheService.addOrgCache(ec.getOrgId());
        }
        if (Objects.isNull(orgId) && Objects.isNull(examId)) {
            throw new BusinessException(ExceptionResultEnum.ORG_ID_OR_EXAM_ID_NOT_CHOOSE);
        }
        if (Objects.nonNull(orgId)) {
            tbOrg = themisCacheService.addOrgCache(orgId);
        }
        if (Objects.isNull(tbOrg)) {
            throw new BusinessException(ExceptionResultEnum.ORG_NO);
        }
        if (Objects.nonNull(tbOrg.getEnable()) && tbOrg.getEnable().intValue() == 0) {
            throw new BusinessException(ExceptionResultEnum.ORG_ENABLE);
        }
        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).eq(TEStudent::getOrgId, tbOrg.getId());
        TEStudent user = teStudentService.getOne(wrapper);
        //学生不存在
        if (Objects.isNull(user)) {
            throw new BusinessException(ExceptionResultEnum.STUDENT_NO);
        }
        if (StringUtils.isNotBlank(user.getBasePhotoPath())) {
            user.setBasePhotoPath(
                    ossUtil.getAliYunOssPublicDomain().getPublicUrl() + File.separator + user.getBasePhotoPath());
        }
        //        String loginPassword = AesUtil.decryptCs7(password, Constants.AES_RULE);
        //        密码错误
        //        String aesPassword = AesUtil.decryptCs7(user.getPassword(), Constants.AES_RULE);
        if (!Objects.equals(password, user.getPassword())) {
            throw new BusinessException(ExceptionResultEnum.PASSWORD_ERROR);
        }
        String lockKey = SystemConstant.REDIS_LOCK_STUDENT_PREFIX + user.getId();
        redisUtil.waitLock(lockKey, SystemConstant.REDIS_LOCK_STUDENT_TIME_OUT);
        try {
            //判断是否有正在考试的记录
            Long examingRecordId = ExamingDataCacheUtil.getExamingRecordId(user.getId());
            if (Objects.nonNull(examingRecordId)) {
                WebsocketStatusEnum sta = ExamRecordCacheUtil.getClientWebsocketStatus(examingRecordId);
                //有正在考试的记录，且考生端websocket在线则不允许登陆
                if (WebsocketStatusEnum.ON_LINE.equals(sta)) {
                    throw new BusinessException(ExceptionResultEnum.STUDENT_NOT_ALLOW_LOGIN);
                }
            }
            return userLoginCommon(user, examId, orgId);
        } finally {
            redisUtil.releaseLock(lockKey);
        }
    }

    /**
     * 学生登录公用
     *
     * @param teStudent
     * @param examId
     * @param orgId
     * @return
     * @throws NoSuchAlgorithmException
     */
    private Result userLoginCommon(TEStudent teStudent, Long examId, Long orgId) throws NoSuchAlgorithmException {
        //停用
        if (teStudent.getEnable().intValue() == 0) {
            throw new BusinessException(ExceptionResultEnum.STUDENT_ENABLE);
        }
        Platform platform = ServletUtil.getRequestPlatform();
        String deviceId = ServletUtil.getRequestDeviceId();
        //添加用户鉴权缓存
        AuthDto authDto = themisCacheService.addStudentAuthCache(teStudent.getId());
        //生成token
        String token = SystemConstant.getNanoId();
        themisCacheService.addStudentAccountCache(teStudent.getId());
        //判断会话来源
        Source source = null;
        if (Platform.WIN == platform || Platform.MAC == platform) {
            source = Source.OE_CLIENT;
        } else {
            throw new BusinessException(ExceptionResultEnum.PLATFORM_INVALID);
        }
        //添加用户会话缓存
        String sessionId = SessionUtil
                .digest(teStudent.getId(), Math.abs(authDto.getRoleCodes().toString().hashCode()), source);

        ExpireTimeDTO expireTime = SystemConstant.getExpireTime(source, platform);
        TBSession tbSession = new TBSession(teStudent.getOrgId(), sessionId, String.valueOf(teStudent.getId()),
                authDto.getRoleCodes().toString(), source.name(), platform.name(), deviceId,
                ServletUtil.getRequest().getLocalAddr(), token, expireTime.getDate().getTime());
        redisUtil.setUserSession(sessionId, tbSession, expireTime.getExpireSeconds());
        //mq发送消息start
        MqDto mqDto = new MqDto(mqUtil.getTopic(), platform.name(), SystemOperationEnum.SESSION,
                sessionId);
        mqDtoService.assembleSendAsyncMsg(mqDto);

        MqDto mqDtoLog = new MqDto(mqUtil.getTopic(), MqTagEnum.STUDENT.name(),
                SystemOperationEnum.LOGIN, MqTagEnum.STUDENT, String.valueOf(teStudent.getId()),
                IpUtil.getRemoteIp(ServletUtil.getRequest()));
        mqDtoService.assembleSendAsyncMsg(mqDtoLog);
        //mq发送消息end
        //测试
        //        String test = SignatureInfo.build(SignatureType.TOKEN, sessionId, token);
        Map<String, Object> map = new HashMap<>();
        //获取未完考试
        Long recordId = ExamingDataCacheUtil.getExamingRecordId(teStudent.getId());
        if (Objects.isNull(recordId)) {
            map = this.getWaitList(teStudent.getId(), examId, orgId, map);
        } else {
            //获取考试记录缓存
            ExamRecordStatusEnum status = ExamRecordCacheUtil.getStatus(recordId);
            if (Objects.isNull(status)) {
                //map = this.getWaitList(teStudent.getId(), examId, orgId, map);
                log.error("ExamRecordStatus in cache is null, recordId=" + recordId);
                throw new BusinessException(ExceptionResultEnum.EXAM_STATUS_ERROR);
            } else {
                Long ecExamId = ExamRecordCacheUtil.getExamId(recordId);
                Long examStudentId = ExamRecordCacheUtil.getExamStudentId(recordId);
                Long examActivityId = ExamRecordCacheUtil.getExamActivityId(recordId);
                ExamCacheBean ec = teExamService.getExamCacheBean(ecExamId);//考试缓存
                ExamActivityCacheBean examActivityCacheBean = teExamActivityService
                        .getExamActivityCacheBean(examActivityId);//考试场次缓存
                ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
                //如果断点时间大于整体断点时间，则强制交卷
                if (Objects.equals(status, ExamRecordStatusEnum.ANSWERING) || Objects
                        .equals(status, ExamRecordStatusEnum.BREAK_OFF) || Objects
                        .equals(status, ExamRecordStatusEnum.RESUME_PREPARE)) {
                    //只有ANSWERING状态才生成断点
                    if (Objects.equals(status, ExamRecordStatusEnum.ANSWERING)) {
                        ExamConstant.sendExamStopMsg(recordId, true, false);
                        tOeExamRecordService.setExamBreak(recordId);
                    }
                    Boolean finished = tOeExamRecordService.examBreakLogic(recordId, false);
                    if (finished) {
                        map = this.getWaitList(teStudent.getId(), examId, orgId, map);
                    } else {
                        ExamUnFinishBean examUnFinishBean = this
                                .unFinishCommon(recordId, ec, examStudentCacheBean, examActivityCacheBean,
                                        examStudentId);
                        map.put("unFinished", examUnFinishBean);
                    }
                } else {
                    map = this.getWaitList(teStudent.getId(), examId, orgId, map);
                }
            }
        }
        //获取全局考试配置
        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);
        map.put(SystemConstant.VERSION, SystemConstant.VERSION_VALUE);
        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 = themisCacheService.addStudentAuthCache(teStudent.getId());
        redisUtil.deleteUserSession(tbSession.getId());
        //循环检查该用户下其他平台是否存在session,不存在则删除用户缓存和鉴权缓存
        boolean delete = true;
        for (Source s : Source.values()) {
            String sessionId = SessionUtil
                    .digest(teStudent.getId(), Math.abs(authDto.getRoleCodes().toString().hashCode()), s.name());
            if (Objects.nonNull(redisUtil.getUserSession(sessionId))) {
                delete = false;
                break;
            }
        }
        if (delete) {
            themisCacheService.removeStudentAuthCache(teStudent.getId());
            themisCacheService.removeStudentAccountCache(teStudent.getId());
        }
        //mq发送消息start
        MqDto mqDto = new MqDto(mqUtil.getTopic(), MqTagEnum.STUDENT.name(),
                SystemOperationEnum.LOGOUT, MqTagEnum.STUDENT, String.valueOf(teStudent.getId()),
                IpUtil.getRemoteIp(ServletUtil.getRequest()));
        mqDtoService.assembleSendAsyncMsg(mqDto);
        //mq发送消息end
        return ResultUtil.ok(true);
    }

    /**
     * 未完成公用
     *
     * @param recordId
     * @param ec
     * @param examStudentCacheBean
     * @param examActivityCacheBean
     * @param examStudentId
     * @return
     */
    private ExamUnFinishBean unFinishCommon(Long recordId, ExamCacheBean ec, ExamStudentCacheBean examStudentCacheBean,
                                            ExamActivityCacheBean examActivityCacheBean, Long examStudentId) {
        ExamCourseCacheBean examCourseCacheBean = teExamCourseService
                .getExamCourseCacheBean(ec.getId(), examStudentCacheBean.getCourseCode());
        TEExamActivityWaitDto teExamActivityWaitDto = new TEExamActivityWaitDto(ec, examActivityCacheBean,
                examStudentCacheBean, examStudentId, examCourseCacheBean, ExamRecordCacheUtil.getStartTime(recordId),
                ExamRecordCacheUtil.getEndTime(recordId), ExamRecordCacheUtil.getOpeningSeconds(recordId),
                ExamRecordCacheUtil.getMinDurationSeconds(recordId),
                ExamRecordCacheUtil.getMaxDurationSeconds(recordId), ExamRecordCacheUtil.getForceFinish(recordId));
        ExamActivityUnFinishBean examActivityUnFinishBean = GsonUtil
                .fromJson(GsonUtil.toJson(teExamActivityWaitDto), ExamActivityUnFinishBean.class);
        examActivityUnFinishBean.setRecordId(recordId);
        return new ExamUnFinishBean(ec.getId(), ec.getName(), examActivityUnFinishBean);
    }

    /**
     * 获取待考列表
     *
     * @param studentId
     * @param examId
     * @param orgId
     * @param map
     * @return
     */
    private Map<String, Object> getWaitList(Long studentId, Long examId, Long orgId, Map<String, Object> map) {
        //获取待考列表
        List<TEExamWaitDto> list = teExamService.getWaitingExam(studentId, examId, orgId);
        if (Objects.nonNull(list) && list.size() > 0) {
            map.put("waiting", list);
        }
        return map;
    }

    @ApiOperation(value = "学生修改密码接口")
    @RequestMapping(value = "/updatePwd", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class)})
    public Result studentUpdatePwd(@Validated @RequestBody StudentParams studentParams, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return ResultUtil.error(bindingResult.getAllErrors().get(0).getDefaultMessage());
        }
        teStudentService.updatePwd(studentParams);
        return ResultUtil.ok(true);
    }
}