package com.qmth.sop.business.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qmth.sop.business.bean.dto.DataPermissionDto;
import com.qmth.sop.business.bean.dto.DingSubmitExportDto;
import com.qmth.sop.business.bean.params.DingSubmitParam;
import com.qmth.sop.business.bean.result.DingSubmitResult;
import com.qmth.sop.business.bean.result.DingSubmitSubTotalResult;
import com.qmth.sop.business.entity.SysUser;
import com.qmth.sop.business.entity.TBDingSubmit;
import com.qmth.sop.business.entity.TFFlowApprove;
import com.qmth.sop.business.mapper.TBDingSubmitMapper;
import com.qmth.sop.business.service.SysUserService;
import com.qmth.sop.business.service.TBDingSubmitService;
import com.qmth.sop.common.contant.SystemConstant;
import com.qmth.sop.common.enums.*;
import com.qmth.sop.common.util.DateDisposeUtils;
import com.qmth.sop.common.util.ServletUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Description: 考勤提交服务实现类
 * @Author: CaoZixuan
 * @Date: 2023/08/25
 */
@Service
public class TBDingSubmitServiceImpl extends ServiceImpl<TBDingSubmitMapper, TBDingSubmit> implements TBDingSubmitService {

    @Resource
    private SysUserService sysUserService;

    @Resource
    private TBDingServiceImpl tbDingService;

    @Override
    public IPage<DingSubmitResult> findDingSubmitPage(Long serviceUnitId, String dingSubmitStatusDesc, Long submitUserId,
            String archivesName, Long supplierId, String customName, String sopNo, Integer violationHoursLimit, Integer dingExceptionLimit, Integer exceptionLimit,
            Integer pageNumber, Integer pageSize) {

        List<DingSubmitStatusEnum> dingSubmitStatusEnumList = new ArrayList<>();
        if (dingSubmitStatusDesc != null && dingSubmitStatusDesc.length() > 0) {
            dingSubmitStatusEnumList = Arrays.stream(DingSubmitStatusEnum.values())
                    .filter(e -> e.getDingSubmitQuery().equals(dingSubmitStatusDesc)).collect(Collectors.toList());
        }
        archivesName = SystemConstant.translateSpecificSign(archivesName);
        customName = SystemConstant.translateSpecificSign(customName);

        SysUser requestUser = (SysUser) ServletUtil.getRequestUser();
        DataPermissionDto dpr = sysUserService.buildUserDataPermission(requestUser.getId());
        if (dpr.getHasThirdPmo()) {
            // 乙方PMO
            Long userSupplierId = requestUser.getSupplierId();
            if (userSupplierId == null || userSupplierId == 0) {
                throw ExceptionResultEnum.ERROR.exception(String.format("请先在用户管理中绑定[%s]对应的供应商", RoleTypeEnum.THIRD_PMO.getDesc()));
            }
            supplierId = userSupplierId;
        }
        IPage<DingSubmitResult> page = this.baseMapper.findDingSubmitPage(new Page<>(pageNumber, pageSize),
                serviceUnitId, dingSubmitStatusEnumList, submitUserId, archivesName, supplierId, customName, sopNo,
                violationHoursLimit, dingExceptionLimit, exceptionLimit, dpr);
        page.getRecords().forEach(e -> {
            String sopRoleTypeStr = e.getSopRoleTypeStr();
            if (SystemConstant.strNotNull(sopRoleTypeStr)) {
                List<SopRoleTypeEnum> sopRoleTypeEnumList = JSONArray.parseArray(sopRoleTypeStr, SopRoleTypeEnum.class);
                e.setRoleName(
                        sopRoleTypeEnumList.stream().map(SopRoleTypeEnum::getTitle).collect(Collectors.joining(",")));
            }
        });
        return page;
    }

    @Override
    public DingSubmitSubTotalResult findDingSubmitSubTotal(Long serviceUnitId, String dingSubmitStatusDesc, Long submitUserId,
            String archivesName, Long supplierId, String customName, String sopNo, Integer violationHoursLimit, Integer dingExceptionLimit, Integer exceptionLimit) {
        List<DingSubmitStatusEnum> dingSubmitStatusEnumList = new ArrayList<>();
        if (dingSubmitStatusDesc != null && dingSubmitStatusDesc.length() > 0) {
            dingSubmitStatusEnumList = Arrays.stream(DingSubmitStatusEnum.values())
                    .filter(e -> e.getSubmitDesc().equals(dingSubmitStatusDesc)).collect(Collectors.toList());
        }
        archivesName = SystemConstant.translateSpecificSign(archivesName);
        customName = SystemConstant.translateSpecificSign(customName);

        SysUser requestUser = (SysUser) ServletUtil.getRequestUser();
        DataPermissionDto dpr = sysUserService.buildUserDataPermission(requestUser.getId());
        if (dpr.getHasThirdPmo()) {
            // 乙方PMO
            Long userSupplierId = requestUser.getSupplierId();
            if (userSupplierId == null || userSupplierId == 0) {
                throw ExceptionResultEnum.ERROR.exception(String.format("请先在用户管理中绑定[%s]对应的供应商", RoleTypeEnum.THIRD_PMO.getDesc()));
            }
            supplierId = userSupplierId;
        }

        List<DingSubmitResult> dingSubmitResultList = this.baseMapper.findDingSubmitList(serviceUnitId,
                dingSubmitStatusEnumList, submitUserId, null, archivesName, supplierId, customName, sopNo,
                violationHoursLimit, dingExceptionLimit, exceptionLimit, dpr);

        Integer dingCount = dingSubmitResultList.size();
        Integer willSubmitCount = Math.toIntExact(
                dingSubmitResultList.stream().filter(e -> e.getSubmitStatus().getSubmitDesc().equals("待提交")).count());
        Integer submitCount = Math.toIntExact(
                dingSubmitResultList.stream().filter(e -> e.getSubmitStatus().getSubmitDesc().equals("已提交")).count());
        Integer totalActualDays = dingSubmitResultList.stream().mapToInt(DingSubmitResult::getActualDays).sum();
        Double totalWorkHours = dingSubmitResultList.stream().map(e -> new BigDecimal(String.valueOf(e.getWorkHours())))
                .reduce(BigDecimal::add).orElse(BigDecimal.ZERO).doubleValue();
        DingSubmitSubTotalResult dingSubmitSubTotalResult = new DingSubmitSubTotalResult();
        dingSubmitSubTotalResult.setDingCount(dingCount);
        dingSubmitSubTotalResult.setWillSubmitCount(willSubmitCount);
        dingSubmitSubTotalResult.setSubmitCount(submitCount);
        dingSubmitSubTotalResult.setTotalActualDays(totalActualDays);
        dingSubmitSubTotalResult.setTotalWorkHours(totalWorkHours);
        return dingSubmitSubTotalResult;
    }

    @Transactional
    @Override
    public void submitDingBatch(List<DingSubmitParam> dingSubmitParamList) {
        dingSubmitParamList.forEach(e -> this.dingSubmitOperate(e, DingSubmitOperateEnum.SUBMIT));
    }

    @Transactional
    @Override
    public void applyWithdraw(DingSubmitParam dingSubmitParam) {
        this.dingSubmitOperate(dingSubmitParam, DingSubmitOperateEnum.WITHDRAW);
    }

    @Transactional
    @Override
    public void cancelWithdraw(DingSubmitParam dingSubmitParam) {
        this.dingSubmitOperate(dingSubmitParam, DingSubmitOperateEnum.CANCEL_WITHDRAW);
    }

    @Transactional
    @Override
    public void agreeWithdraw(Long dingSubmitId) {
        TBDingSubmit tbDingSubmit = this.getById(dingSubmitId);
        if (Objects.isNull(tbDingSubmit)) {
            throw ExceptionResultEnum.ERROR.exception("未找到工时统计记录");
        }
        DingSubmitStatusEnum status = tbDingSubmit.getSubmitStatus();
        if (!DingSubmitStatusEnum.APPLY_WITHDRAW.equals(status)) {
            throw ExceptionResultEnum.ERROR.exception("只有[申请撤回]状态的可以同意");
        }
        tbDingSubmit.setSubmitStatus(DingSubmitStatusEnum.AGREE_WITHDRAW);
        this.updateById(tbDingSubmit);
    }

    @Override
    public List<DingSubmitExportDto> findDingSubmitExportList(Long serviceUnitId, String dingSubmitStatusDesc, Long submitUserId, String archivesName, Long supplierId, String customName, String sopNo,
            Integer violationHoursLimit, Integer dingExceptionLimit, Integer exceptionLimit, Long requestUserId) {
        List<DingSubmitStatusEnum> dingSubmitStatusEnumList = new ArrayList<>();
        if (dingSubmitStatusDesc != null && dingSubmitStatusDesc.length() > 0) {
            dingSubmitStatusEnumList = Arrays.stream(DingSubmitStatusEnum.values())
                    .filter(e -> e.getDingSubmitQuery().equals(dingSubmitStatusDesc)).collect(Collectors.toList());
        }
        archivesName = SystemConstant.translateSpecificSign(archivesName);
        customName = SystemConstant.translateSpecificSign(customName);

        DataPermissionDto dpr = sysUserService.buildUserDataPermission(requestUserId);
        if (dpr.getHasThirdPmo()) {
            SysUser requestUser = sysUserService.getById(requestUserId);
            // 乙方PMO
            Long userSupplierId = requestUser.getSupplierId();
            if (userSupplierId == null || userSupplierId == 0) {
                throw ExceptionResultEnum.ERROR.exception(String.format("请先在用户管理中绑定[%s]对应的供应商", RoleTypeEnum.THIRD_PMO.getDesc()));
            }
            supplierId = userSupplierId;
        }

        List<DingSubmitResult> dingSubmitResultList = this.baseMapper.findDingSubmitList(serviceUnitId,
                dingSubmitStatusEnumList, submitUserId, null, archivesName, supplierId, customName, sopNo,
                violationHoursLimit, dingExceptionLimit, exceptionLimit, dpr);

        return dingSubmitResultList.stream().flatMap(e -> {
            DingSubmitExportDto dto = new DingSubmitExportDto();
            dto.setServiceUnitName(e.getServiceUnitName());
            dto.setSopNo(e.getSopNo());
            dto.setCustomName(e.getCustomName());
            dto.setProvince(e.getProvince());
            dto.setCity(e.getCity());
            if (SystemConstant.longNotNull(e.getScanStartTime())) {
                dto.setScanStartTime(DateDisposeUtils.getDateStr(e.getScanStartTime()));
            }
            if (SystemConstant.longNotNull(e.getScanEndTime())) {
                dto.setScanEndTime(DateDisposeUtils.getDateStr(e.getScanEndTime()));
            }
            if (SystemConstant.longNotNull(e.getScanStartTime())) {
                dto.setMarkPaperStartTime(DateDisposeUtils.getDateStr(e.getMarkPaperStartTime()));
            }
            if (SystemConstant.longNotNull(e.getScanStartTime())) {
                dto.setMarkPaperEndTime(DateDisposeUtils.getDateStr(e.getMarkPaperEndTime()));
            }
            dto.setArchivesInfo(e.getArchivesInfo());
            String sopRoleTypeStr = e.getSopRoleTypeStr();
            if (SystemConstant.strNotNull(sopRoleTypeStr)) {
                List<SopRoleTypeEnum> sopRoleTypeEnumList = JSONArray.parseArray(sopRoleTypeStr, SopRoleTypeEnum.class);
                dto.setRoleType(
                        sopRoleTypeEnumList.stream().map(SopRoleTypeEnum::getTitle).collect(Collectors.joining(",")));
            }
            dto.setSupplierName(e.getSupplierName());
            dto.setActualDays(e.getActualDays());
            dto.setWeekdays(e.getWeekdays());
            dto.setWeekends(e.getWeekends());
            dto.setLegalHolidays(e.getLegalHolidays());
            dto.setWorkHours(e.getWorkHours());
            dto.setViolationDays(e.getViolationDays());
            dto.setDingExceptionCount(e.getDingExceptionCount());
            dto.setRemainCount(tbDingService.findRemainCount(e.getUserId(), serviceUnitId, e.getSopNo()));
            dto.setExceptionCount(e.getExceptionCount());
            dto.setSubmitStatus(e.getSubmitStatus().getSubmitDesc());
            dto.setSubmitUserName(e.getSubmitUserName());
            if (e.getSubmitTime() != null && e.getSubmitTime() > 0) {
                dto.setSubmitTime(DateDisposeUtils.getDateStr(e.getSubmitTime()));
            }
            return Stream.of(dto);
        }).collect(Collectors.toList());
    }

    @Override
    public boolean canUpdateDingSetting(String sopNo) {
        boolean result = true;
        List<TBDingSubmit> tbDingSubmitList = this.list(
                new QueryWrapper<TBDingSubmit>().lambda().eq(TBDingSubmit::getSopNo, sopNo));
        if (CollectionUtils.isNotEmpty(tbDingSubmitList)) {
            for (TBDingSubmit tbDingSubmit : tbDingSubmitList) {
                DingSubmitStatusEnum submitStatus = tbDingSubmit.getSubmitStatus();
                if (!DingSubmitStatusEnum.WILL_SUBMIT.equals(submitStatus) && !DingSubmitStatusEnum.AGREE_WITHDRAW.equals(submitStatus)) {
                    result = false;
                    break;
                }
            }
        }
        return result;
    }

    @Override
    public boolean canUpdateDingSetting(Long serviceId, String sopNo, Long userId) {
        boolean result = true;
        QueryWrapper<TBDingSubmit> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(TBDingSubmit::getServiceId, serviceId).eq(TBDingSubmit::getUserId, userId).last(SystemConstant.LIMIT1);
        if (SystemConstant.strNotNull(sopNo)) {
            queryWrapper.lambda().eq(TBDingSubmit::getSopNo, sopNo);
        } else {
            queryWrapper.lambda().isNull(TBDingSubmit::getSopNo);
        }

        TBDingSubmit tbDingSubmit = this.getOne(queryWrapper);
        if (Objects.nonNull(tbDingSubmit)) {
            DingSubmitStatusEnum submitStatus = tbDingSubmit.getSubmitStatus();
            if (!DingSubmitStatusEnum.WILL_SUBMIT.equals(submitStatus) && !DingSubmitStatusEnum.AGREE_WITHDRAW.equals(
                    submitStatus)) {
                result = false;
            }
        }
        return result;
    }

    private void dingSubmitOperate(DingSubmitParam dingSubmitParam, DingSubmitOperateEnum dingSubmitOperateType) {
        SysUser requestUser = (SysUser) ServletUtil.getRequestUser();
        String sopNo = dingSubmitParam.getSopNo();
        Long serviceId = dingSubmitParam.getServiceUnitId();
        Long userArchivesId = dingSubmitParam.getUserArchivesId();

        // sopNo 为空，则表示为区协操作
        boolean coordinatorOperate = !SystemConstant.strNotNull(sopNo);
        List<DingSubmitResult> dingSubmitResultList = this.baseMapper.findDingSubmitList(serviceId, null, null,
                userArchivesId, null, null, null, sopNo, null, null, null, null);

        if (coordinatorOperate) {
            // 如果是区协操作，只过滤出sopNo为空的数据
            dingSubmitResultList = dingSubmitResultList.stream().filter(e -> !SystemConstant.strNotNull(e.getSopNo())).collect(Collectors.toList());
        }
        if (dingSubmitResultList.size() != 1) {
            throw ExceptionResultEnum.ERROR.exception(
                    String.format("未找到sop流水号为[%s],用户档案号为[%s]的考勤信息", sopNo, userArchivesId));
        }
        DingSubmitResult dingSubmitResult = dingSubmitResultList.get(0);
        String serviceUnitName = dingSubmitResult.getServiceUnitName();
        String userArchivesName = dingSubmitResult.getArchivesName();
        DingSubmitStatusEnum submitStatus = dingSubmitResult.getSubmitStatus();
        TBDingSubmit tbDingSubmit = new TBDingSubmit();
        tbDingSubmit.setServiceId(dingSubmitResult.getServiceUnitId());
        tbDingSubmit.setSopNo(sopNo);
        tbDingSubmit.setCrmNo(dingSubmitResult.getCrmNo());
        tbDingSubmit.setUserId(dingSubmitResult.getUserId());
        tbDingSubmit.setUserArchivesId(userArchivesId);
        tbDingSubmit.setScanStartTime(dingSubmitResult.getScanStartTime());
        tbDingSubmit.setScanEndTime(dingSubmitResult.getScanEndTime());
        tbDingSubmit.setMarkPaperStartTime(dingSubmitResult.getMarkPaperStartTime());
        tbDingSubmit.setMarkPaperEndTime(dingSubmitResult.getMarkPaperEndTime());
        String sopRoleTypeStr = dingSubmitResult.getSopRoleTypeStr();
        if (SystemConstant.strNotNull(sopRoleTypeStr)) {
            List<SopRoleTypeEnum> sopRoleTypeEnumList = JSONArray.parseArray(sopRoleTypeStr, SopRoleTypeEnum.class);
            tbDingSubmit.setRoleName(
                    sopRoleTypeEnumList.stream().map(SopRoleTypeEnum::getTitle).collect(Collectors.joining(",")));
        }
        tbDingSubmit.setActualDays(dingSubmitResult.getActualDays());
        tbDingSubmit.setWeekdays(dingSubmitResult.getWeekdays());
        tbDingSubmit.setWeekends(dingSubmitResult.getWeekends());
        tbDingSubmit.setLegalHolidays(dingSubmitResult.getLegalHolidays());
        tbDingSubmit.setWorkHours(dingSubmitResult.getWorkHours());
        tbDingSubmit.setViolationDays(dingSubmitResult.getViolationDays());
        tbDingSubmit.setSubmitUserId(requestUser.getId());
        tbDingSubmit.setSubmitTime(System.currentTimeMillis());
        tbDingSubmit.setSubmitStatus(DingSubmitStatusEnum.ALREADY_SUBMIT);

        switch (dingSubmitOperateType) {
        case SUBMIT:
            // 条件1 - 提交必须是待提交状态
            List<DingSubmitStatusEnum> dingSubmitStatusEnumList = new ArrayList<>();
            dingSubmitStatusEnumList.add(DingSubmitStatusEnum.WILL_SUBMIT);
            dingSubmitStatusEnumList.add(DingSubmitStatusEnum.AGREE_WITHDRAW);
            if (!dingSubmitStatusEnumList.contains(submitStatus)) {
                throw ExceptionResultEnum.ERROR.exception(
                        String.format("服务单元为[%s],sop流水号为[%s],用户档案为[%s]的考勤的提交状态为[%s],不能提交", serviceUnitName, sopNo,
                                userArchivesName, submitStatus.getSubmitDesc()));
            }
            // 条件2 - 待处理的异常数为0
            Integer exceptionCount = dingSubmitResult.getExceptionCount();
            if (exceptionCount != 0) {
                throw ExceptionResultEnum.ERROR.exception(
                        String.format("服务单元为[%s],sop流水号为[%s],用户档案为[%s]的考勤待处理异常数为[%s],不能提交", serviceUnitName, sopNo,
                                userArchivesName, exceptionCount));
            }

            // 条件3 - SOP结束后的第二天才能提交考勤
            if (!coordinatorOperate) {
                TFFlowApprove tfFlowApproves = tbDingService.findFlowApproveBySopNo(sopNo);
                FlowStatusEnum flowStatus = tfFlowApproves.getStatus();
                if (!FlowStatusEnum.FINISH.equals(flowStatus)) {
                    throw ExceptionResultEnum.ERROR.exception(
                            String.format("服务单元为[%s],sop流水号为[%s],用户档案为[%s]的sop状态为[%s],sop结束后次日才能提交考勤", serviceUnitName,
                                    sopNo, userArchivesName, flowStatus.getTitle()));
                }
                Long endTime = tfFlowApproves.getUpdateTime();
                Calendar calendar = Calendar.getInstance();
                calendar.setTimeInMillis(endTime);
                calendar.set(Calendar.HOUR_OF_DAY, 0);
                calendar.set(Calendar.MINUTE, 0);
                calendar.set(Calendar.SECOND, 0);
                calendar.add(Calendar.DATE, 1);
                long nextDayEarliestTime = calendar.getTimeInMillis();
                if (System.currentTimeMillis() < nextDayEarliestTime) {
                    throw ExceptionResultEnum.ERROR.exception(
                            String.format("服务单元为[%s],sop流水号为[%s],用户档案为[%s]的考勤还未到提交时间,sop结束后次日才能提交考勤", serviceUnitName,
                                    sopNo, userArchivesName));
                }
            }

            Long submitId = dingSubmitResult.getSubmitId();
            if (SystemConstant.longNotNull(submitId)) {
                tbDingSubmit.setId(submitId);
            }
            tbDingSubmit.setSubmitStatus(DingSubmitStatusEnum.ALREADY_SUBMIT);
            break;
        case WITHDRAW:
            if (!DingSubmitStatusEnum.ALREADY_SUBMIT.equals(submitStatus)) {
                throw ExceptionResultEnum.ERROR.exception(
                        String.format("服务单元为[%s],sop流水号为[%s],用户档案为[%s]的考勤的提交状态为[%s],不能申请撤回", serviceUnitName, sopNo,
                                userArchivesName, submitStatus.getSubmitDesc()));
            }
            if (dingSubmitResult.getSubmitId() == null || dingSubmitResult.getSubmitId() == 0) {
                throw ExceptionResultEnum.ERROR.exception(
                        String.format("服务单元为[%s],sop流水号为[%s],用户档案为[%s]的考勤数据异常", serviceUnitName, sopNo, userArchivesName));
            }
            tbDingSubmit.setId(dingSubmitResult.getSubmitId());
            tbDingSubmit.setSubmitStatus(DingSubmitStatusEnum.APPLY_WITHDRAW);
            break;
        case CANCEL_WITHDRAW:
            if (!DingSubmitStatusEnum.APPLY_WITHDRAW.equals(submitStatus)) {
                throw ExceptionResultEnum.ERROR.exception(
                        String.format("服务单元为[%s],sop流水号为[%s],用户档案为[%s]的考勤的提交状态为[%s],不能申请取消撤回", serviceUnitName, sopNo,
                                userArchivesName, submitStatus.getSubmitDesc()));
            }
            if (dingSubmitResult.getSubmitId() == null || dingSubmitResult.getSubmitId() == 0) {
                throw ExceptionResultEnum.ERROR.exception(
                        String.format("服务单元为[%s],sop流水号为[%s],用户档案为[%s]的考勤数据异常", serviceUnitName, sopNo, userArchivesName));
            }
            tbDingSubmit.setId(dingSubmitResult.getSubmitId());
            tbDingSubmit.setSubmitStatus(DingSubmitStatusEnum.ALREADY_SUBMIT);
        }
        this.saveOrUpdate(tbDingSubmit);
    }
}
