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

import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qmth.sop.business.bean.dto.*;
import com.qmth.sop.business.bean.result.CrmProjectResult;
import com.qmth.sop.business.bean.result.UserArchivesResult;
import com.qmth.sop.business.entity.*;
import com.qmth.sop.business.mapper.TBDingStatisticMapper;
import com.qmth.sop.business.service.*;
import com.qmth.sop.common.contant.SystemConstant;
import com.qmth.sop.common.enums.DingDateTypeEnum;
import com.qmth.sop.common.enums.DingStatisticTypeEnum;
import com.qmth.sop.common.enums.ExceptionResultEnum;
import com.qmth.sop.common.enums.SopRoleTypeEnum;
import com.qmth.sop.common.util.DateDisposeUtils;
import com.qmth.sop.common.util.FileUtil;
import com.qmth.sop.common.util.ServletUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * <p>
 * 考勤提交统计表 服务实现类
 * </p>
 *
 * @author CaoZixuan
 * @since 2024-05-22
 */
@Service
public class TBDingStatisticServiceImpl extends ServiceImpl<TBDingStatisticMapper, TBDingStatistic> implements TBDingStatisticService {

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

    @Resource
    private TBCrmDetailService tbCrmDetailService;

    @Resource
    private TBDingService tbDingService;

    @Resource
    private TBCrmService tbCrmService;

    @Resource
    private TBUserArchivesService tbUserArchivesService;

    @Resource
    private TBDingApplyService tbDingApplyService;

    @Resource
    private TBDingSubmitService tbDingSubmitService;

    @Resource
    private TBServiceService tbServiceService;

    @Resource
    private TBDingStatisticByPersonService tbdingStatisticByPersonService;

    @Resource
    private TBDingStatisticByCrmService tbdingStatisticByCrmService;

    @Resource
    private TBDingStatisticBySopService tbdingStatisticBySopService;

    @Resource
    private TBDingStatisticByCustomService tbdingStatisticByCustomService;

    @Resource
    private TBDingStatisticBySupplierService tbdingStatisticBySupplierService;

    @Override
    public TBDingStatistic findBySopNoAndUserArchivesId(Long serviceId, String sopNo, Long userArchivesId) {
        QueryWrapper<TBDingStatistic> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(TBDingStatistic::getServiceId, serviceId).eq(TBDingStatistic::getUserArchivesId, userArchivesId).last(SystemConstant.LIMIT1);
        if (SystemConstant.strNotNull(sopNo)) {
            queryWrapper.lambda().eq(TBDingStatistic::getSopNo, sopNo);
        } else {
            queryWrapper.lambda().isNull(TBDingStatistic::getSopNo);
        }
        return this.getOne(queryWrapper);
    }

    @Override
    public void buildDingStatistic(String sopNo, Long userArchivesId) {
        Long currentEarliestTime = DateDisposeUtils.getEarliestTime(System.currentTimeMillis());
        List<TBDing> datasource = tbDingService.list(new QueryWrapper<TBDing>().lambda().eq(TBDing::getSopNo, sopNo).eq(TBDing::getUserArchivesId, userArchivesId)).stream().filter(e -> {
            if (Objects.nonNull(e.getSignInTime()) && Objects.nonNull(e.getSignOutTime())) {
                return e.getSignInTime() < currentEarliestTime && e.getSignOutTime() < currentEarliestTime;
            } else if (Objects.isNull(e.getSignInTime())) {
                return e.getSignOutTime() < currentEarliestTime;
            } else if (Objects.isNull(e.getSignOutTime())) {
                return e.getSignInTime() < currentEarliestTime;
            } else {
                return true;
            }
        }).collect(Collectors.toList());
        this.buildDingStatisticBySop(sopNo, datasource, -1L);
    }

    @Transactional
    @Override
    public void buildDingStatistic(String sopNo) {
        // 统计前一天的数据
        Long currentEarliestTime = DateDisposeUtils.getEarliestTime(System.currentTimeMillis());
        List<TBDing> datasource = tbDingService.list(new QueryWrapper<TBDing>().lambda().eq(TBDing::getSopNo, sopNo)).stream().filter(e -> {
            if (Objects.nonNull(e.getSignInTime()) && Objects.nonNull(e.getSignOutTime())) {
                return e.getSignInTime() < currentEarliestTime && e.getSignOutTime() < currentEarliestTime;
            } else if (Objects.isNull(e.getSignInTime())) {
                return e.getSignOutTime() < currentEarliestTime;
            } else if (Objects.isNull(e.getSignOutTime())) {
                return e.getSignInTime() < currentEarliestTime;
            } else {
                return true;
            }
        }).collect(Collectors.toList());
        this.buildDingStatisticBySop(sopNo, datasource, -1L);
    }

    @Transactional
    @Override
    public void buildDingStatistic() {
        Long currentEarliestTime = DateDisposeUtils.getEarliestTime(System.currentTimeMillis());

        // 统计前一天的数据
        List<TBDing> datasource = tbDingService.list().stream().filter(e -> {
            if (Objects.nonNull(e.getSignInTime()) && Objects.nonNull(e.getSignOutTime())) {
                return e.getSignInTime() < currentEarliestTime && e.getSignOutTime() < currentEarliestTime;
            } else if (Objects.isNull(e.getSignInTime())) {
                return e.getSignOutTime() < currentEarliestTime;
            } else if (Objects.isNull(e.getSignOutTime())) {
                return e.getSignInTime() < currentEarliestTime;
            } else {
                return true;
            }
        }).collect(Collectors.toList());
        // 工程师考勤统计（sopNo不为空的 按照sopNo统计）
        List<String> sopNoList = datasource.stream().map(TBDing::getSopNo).filter(SystemConstant::strNotNull).distinct()
                .collect(Collectors.toList());
        for (String sopNo : sopNoList) {
            List<TBDing> sopDingInfo = datasource.stream().filter(e -> SystemConstant.strNotNull(e.getSopNo()) && e.getSopNo().equals(sopNo))
                    .collect(Collectors.toList());
            this.buildDingStatisticBySop(sopNo, sopDingInfo, -1L);
        }

        // 区协考勤统计 （sopNo为空的 按照服务单元统计）
        List<Long> serviceIdList = datasource.stream().filter(e -> !SystemConstant.strNotNull(e.getSopNo())).map(TBDing::getServiceId).distinct().collect(Collectors.toList());
        for (Long serviceId : serviceIdList) {
            List<TBDing> serviceDingInfo = datasource.stream().filter(e -> e.getServiceId().equals(serviceId) && !SystemConstant.strNotNull(e.getSopNo()))
                    .collect(Collectors.toList());
            this.buildDingStatisticByService(serviceId, serviceDingInfo, -1L);
        }
    }

    @Transactional
    @Override
    public void buildDingStatisticBySop(String sopNo, List<TBDing> datasource, Long requestUserId) {
        List<TBDingStatistic> tbDingStatisticList = new ArrayList<>();
        TBCrmDetail tbCrmDetail = tbCrmDetailService.findBySopNo(sopNo);
        if (Objects.isNull(tbCrmDetail)) {
            log.error(String.format("sopNo[%s]未找到派单详情信息", sopNo));
            throw ExceptionResultEnum.ERROR.exception("未找到派单详情信息");
        }

        String crmNo = tbCrmDetail.getCrmNo();
        TBCrm tbCrm = tbCrmService.findByCrmNo(crmNo);
        if (Objects.isNull(tbCrm)) {
            log.error(String.format("crmNo[%s]未找到派单信息", crmNo));
            throw ExceptionResultEnum.ERROR.exception("未找到派单信息");
        }
        CrmProjectResult crmProjectResult = tbCrmService.findCrmProjectInfo(null, null, crmNo);
        Long serviceUnitId = tbCrm.getServiceId();

        // 该sop计划有效的日期
        List<Long> userArchivesIdList = datasource.stream().map(TBDing::getUserArchivesId).distinct().collect(Collectors.toList());

        for (Long userArchivesId : userArchivesIdList) {
            UserArchivesResult tbUserArchives = tbUserArchivesService.findUserArchivesByArchivesIdORUserId(
                    userArchivesId, null);
            if (Objects.isNull(tbUserArchives)) {
                throw ExceptionResultEnum.ERROR.exception("人员档案不存在");
            }
            Long userId = tbUserArchives.getUserId();
            // 打卡数据集合
            List<TBDing> dingList = datasource.stream()
                    .filter(e -> Objects.equals(userArchivesId, e.getUserArchivesId())).collect(Collectors.toList());

            // 非异常打卡（打卡时间在计划时间范围内 以及签到签退完整的）

            List<SopRoleTypeEnum> sopRoleTypeList = dingList.stream().flatMap(e -> {
                String sopRoleTypeStr = e.getSopRoleType();
                List<SopRoleTypeEnum> list = JSONArray.parseArray(sopRoleTypeStr, SopRoleTypeEnum.class);
                return list.stream();
            }).distinct().collect(Collectors.toList());
            String sopRoleType = JSON.toJSONString(sopRoleTypeList);

            // 根据非异常打卡数据统计
            int actualDays = 0;
            int weekDays = 0;
            int weekends = 0;
            int legalHolidays = 0;
            BigDecimal workHours = BigDecimal.ZERO;
            BigDecimal var = new BigDecimal(3600000);
            for (TBDing effectDing : dingList) {
                if (!SystemConstant.isOneNull(effectDing.getSignInTime(), effectDing.getSignOutTime())) {
                    // 有效天数
                    actualDays++;
                    DingDateTypeEnum dateType = effectDing.getDateType();
                    switch (dateType) {
                    case WEEKEND:
                        weekends++;
                        break;
                    case LEGAL_HOLIDAYS:
                        legalHolidays++;
                        break;
                    default:
                        weekDays++;
                        break;
                    }
                    // 累计工时
                    workHours = workHours.add(
                            new BigDecimal(effectDing.getSignOutTime() - effectDing.getSignInTime()).divide(var, 1,
                                    RoundingMode.HALF_UP));
                }
            }
            // 违规工时
            int violationDays = Math.toIntExact(
                    dingList.stream().filter(e -> SystemConstant.isOneNull(e.getSignInTime(), e.getSignOutTime()))
                            .count());
            // 考勤异常数
            int dingExceptionCount = 0;
            for (TBDing tbDing : dingList) {
                Long signInTime = tbDing.getSignInTime();
                Long signOutTime = tbDing.getSignOutTime();
                if (!SystemConstant.longNotNull(signInTime)) {
                    dingExceptionCount++;
                }
                if (!SystemConstant.longNotNull(signOutTime)) {
                    dingExceptionCount++;
                }
            }

            // 剩余补卡数
            int remainCount = tbDingService.findRemainCount(userId, serviceUnitId, sopNo);

            // 待处理异常数
            int exceptionCount = tbDingApplyService.count(
                    new QueryWrapper<TBDingApply>().lambda().eq(TBDingApply::getSopNo, sopNo).isNull(TBDingApply::getApprove));

            TBDingStatistic tbDingStatistic;
            tbDingStatistic = this.findBySopNoAndUserArchivesId(serviceUnitId, sopNo, userArchivesId);
            if (Objects.isNull(tbDingStatistic)) {
                // 新增考勤统计
                tbDingStatistic = new TBDingStatistic();
                tbDingStatistic.insertInfo(requestUserId);
            } else {
                // 更新考勤统计
                if (!tbDingSubmitService.canUpdateDingSetting(serviceUnitId, sopNo, userId)) {
                    // 已提交的考勤不再计算了
                    continue;
                }
                tbDingStatistic.updateInfo(requestUserId);
            }
            tbDingStatistic.setServiceId(serviceUnitId);
            tbDingStatistic.setServiceName(crmProjectResult.getServiceUnitName());
            tbDingStatistic.setSopNo(sopNo);
            tbDingStatistic.setCrmNo(crmNo);
            tbDingStatistic.setCustomId(crmProjectResult.getCustomId());
            tbDingStatistic.setCustomName(crmProjectResult.getCustomName());
            tbDingStatistic.setProvince(crmProjectResult.getCustomProvince());
            tbDingStatistic.setCity(crmProjectResult.getCustomCity());
            tbDingStatistic.setUserId(userId);
            tbDingStatistic.setUserArchivesId(userArchivesId);
            tbDingStatistic.setUserArchivesName(tbUserArchives.getName());
            tbDingStatistic.setUserArchivesCode(tbUserArchives.getCode());
            tbDingStatistic.setScanStartTime(tbCrmDetail.getScanActualStartTime());
            tbDingStatistic.setScanEndTime(tbCrmDetail.getScanActualEndTime());
            tbDingStatistic.setMarkPaperStartTime(tbCrmDetail.getMarkPaperActualStartTime());
            tbDingStatistic.setMarkPaperEndTime(tbCrmDetail.getMarkPaperActualEndTime());
            tbDingStatistic.setRoleName(sopRoleType);
            tbDingStatistic.setSupplierId(tbUserArchives.getSupplierId());
            tbDingStatistic.setSupplierName(tbUserArchives.getSupplierName());
            tbDingStatistic.setActualDays(actualDays);
            tbDingStatistic.setWeekdays(weekDays);
            tbDingStatistic.setWeekends(weekends);
            tbDingStatistic.setLegalHolidays(legalHolidays);
            tbDingStatistic.setWorkHours(workHours);
            tbDingStatistic.setViolationDays(violationDays);
            tbDingStatistic.setDingExceptionCount(dingExceptionCount);
            tbDingStatistic.setRemainCount(remainCount);
            tbDingStatistic.setExceptionCount(exceptionCount);
            tbDingStatisticList.add(tbDingStatistic);
        }
        this.saveOrUpdateBatch(tbDingStatisticList);
    }

    @Override
    public void buildDingStatisticByService(Long serviceId, List<TBDing> datasource, Long requestUserId) {
        List<TBDingStatistic> tbDingStatisticList = new ArrayList<>();
        List<Long> userArchivesIdList = datasource.stream().map(TBDing::getUserArchivesId).distinct().collect(Collectors.toList());

        for (Long userArchivesId : userArchivesIdList) {
            UserArchivesResult tbUserArchives = tbUserArchivesService.findUserArchivesByArchivesIdORUserId(
                    userArchivesId, null);
            if (Objects.isNull(tbUserArchives)) {
                throw ExceptionResultEnum.ERROR.exception("人员档案不存在");
            }
            Long userId = tbUserArchives.getUserId();
            // 打卡数据集合
            List<TBDing> dingList = datasource.stream()
                    .filter(e -> Objects.equals(userArchivesId, e.getUserArchivesId())).collect(Collectors.toList());

            List<SopRoleTypeEnum> sopRoleTypeList = dingList.stream().flatMap(e -> {
                String sopRoleTypeStr = e.getSopRoleType();
                List<SopRoleTypeEnum> list = JSONArray.parseArray(sopRoleTypeStr, SopRoleTypeEnum.class);
                return list.stream();
            }).distinct().collect(Collectors.toList());
            String sopRoleType = JSON.toJSONString(sopRoleTypeList);

            // 根据非异常打卡数据统计
            int actualDays = 0;
            int weekDays = 0;
            int weekends = 0;
            int legalHolidays = 0;
            BigDecimal workHours = BigDecimal.ZERO;
            BigDecimal var = new BigDecimal(3600000);
            for (TBDing effectDing : dingList) {
                // 有效天数
                actualDays++;
                DingDateTypeEnum dateType = effectDing.getDateType();
                switch (dateType) {
                case WEEKEND:
                    weekends++;
                    break;
                case LEGAL_HOLIDAYS:
                    legalHolidays++;
                    break;
                default:
                    weekDays++;
                    break;
                }
                // 累计工时
                workHours = workHours.add(
                        new BigDecimal(effectDing.getSignOutTime() - effectDing.getSignInTime()).divide(var, 1,
                                RoundingMode.HALF_UP));
            }
            // 违规工时
            int violationDays = Math.toIntExact(
                    dingList.stream().filter(e -> SystemConstant.isOneNull(e.getSignInTime(), e.getSignOutTime()))
                            .count());
            // 考勤异常数
            int dingExceptionCount = 0;
            for (TBDing tbDing : dingList) {
                Long signInTime = tbDing.getSignInTime();
                Long signOutTime = tbDing.getSignOutTime();
                if (!SystemConstant.longNotNull(signInTime)) {
                    dingExceptionCount++;
                }
                if (!SystemConstant.longNotNull(signOutTime)) {
                    dingExceptionCount++;
                }
            }

            // 剩余补卡数
            int remainCount = tbDingService.findRemainCount(userId, serviceId, null);

            // 待处理异常数
            int exceptionCount = tbDingApplyService.count(
                    new QueryWrapper<TBDingApply>().lambda().eq(TBDingApply::getServiceId, serviceId)
                            .eq(TBDingApply::getCreateId, userId).isNull(TBDingApply::getApprove));

            TBDingStatistic tbDingStatistic;
            tbDingStatistic = this.findBySopNoAndUserArchivesId(serviceId, null, userArchivesId);
            if (Objects.isNull(tbDingStatistic)) {
                // 新增考勤统计
                tbDingStatistic = new TBDingStatistic();
                tbDingStatistic.insertInfo(requestUserId);
            } else {
                // 更新考勤统计
                if (!tbDingSubmitService.canUpdateDingSetting(serviceId, null, userId)) {
                    // 已提交的考勤不再计算了
                    continue;
                }
                tbDingStatistic.updateInfo(requestUserId);
            }
            tbDingStatistic.setServiceId(serviceId);
            tbDingStatistic.setServiceName(tbServiceService.getById(serviceId).getName());
            tbDingStatistic.setUserId(userId);
            tbDingStatistic.setUserArchivesId(userArchivesId);
            tbDingStatistic.setUserArchivesName(tbUserArchives.getName());
            tbDingStatistic.setUserArchivesCode(tbUserArchives.getCode());
            tbDingStatistic.setRoleName(sopRoleType);
            tbDingStatistic.setSupplierId(tbUserArchives.getSupplierId());
            tbDingStatistic.setSupplierName(tbUserArchives.getSupplierName());
            tbDingStatistic.setActualDays(actualDays);
            tbDingStatistic.setWeekdays(weekDays);
            tbDingStatistic.setWeekends(weekends);
            tbDingStatistic.setLegalHolidays(legalHolidays);
            tbDingStatistic.setWorkHours(workHours);
            tbDingStatistic.setViolationDays(violationDays);
            tbDingStatistic.setDingExceptionCount(dingExceptionCount);
            tbDingStatistic.setRemainCount(remainCount);
            tbDingStatistic.setExceptionCount(exceptionCount);
            tbDingStatisticList.add(tbDingStatistic);
        }
        this.saveOrUpdateBatch(tbDingStatisticList);
    }

    @Transactional
    @Override
    public void buildStatisticData() {
        List<TBDingStatistic> datasource = this.list();
        tbdingStatisticByPersonService.buildStatisticByPerson(datasource);
        tbdingStatisticBySopService.buildStatisticBySop(datasource);
        tbdingStatisticByCrmService.buildStatisticByCrm(datasource);
        tbdingStatisticByCustomService.buildStatisticByCustom(datasource);
        tbdingStatisticBySupplierService.buildStatisticBySupplier(datasource);
    }

    @Override
    public void exportStatisticData(Long serviceId, DingStatisticTypeEnum type) throws IOException {
        File fileTemp = null;
        try {
            fileTemp = SystemConstant.getFileTempVar(SystemConstant.XLSX_PREFIX);

            switch (type) {
            case BY_PERSON:
                List<DingStatisticByPersonDto> byPersonData = tbdingStatisticByPersonService.findStatisticByPersonList(
                        serviceId);
                EasyExcel.write(fileTemp, DingStatisticByPersonDto.class).sheet("工时统计-按人员").doWrite(byPersonData);
                break;
            case BY_SOP:
                List<DingStatisticBySopDto> bySopData = tbdingStatisticBySopService.findStatisticBySopList(serviceId);
                EasyExcel.write(fileTemp, DingStatisticBySopDto.class).sheet("工时统计-按sop").doWrite(bySopData);
                break;
            case BY_CRM:
                List<DingStatisticByCrmDto> byCrmData = tbdingStatisticByCrmService.findStatisticByCrmList(serviceId);
                EasyExcel.write(fileTemp, DingStatisticByCrmDto.class).sheet("工时统计-按crm").doWrite(byCrmData);
                break;
            case BY_CUSTOM:
                List<DingStatisticByCustomDto> byCustomData = tbdingStatisticByCustomService.findStatisticByCustomList(
                        serviceId);
                EasyExcel.write(fileTemp, DingStatisticByCustomDto.class).sheet("工时统计-按客户").doWrite(byCustomData);
                break;
            case BY_SUPPLIER:
                List<DingStatisticBySupplierDto> bySupplierData = tbdingStatisticBySupplierService.findStatisticBySupplierList(
                        serviceId);
                EasyExcel.write(fileTemp, DingStatisticBySupplierDto.class).sheet("工时统计-按供应商").doWrite(bySupplierData);
                break;
            default:
                break;
            }
            HttpServletResponse response = ServletUtil.getResponse();
            FileUtil.outputFile(response, fileTemp, "工时统计结果.xlsx");
        } finally {
            if (Objects.nonNull(fileTemp)) {
                fileTemp.delete();
            }
        }
    }

    /**
     * 查询sop周期
     *
     * @param tbCrmDetail 派单详情
     * @return 结果
     */
    @Override
    public SopPeriodDto findSopPeriod(TBCrmDetail tbCrmDetail) {
        Long scanActualStartTime = tbCrmDetail.getScanActualStartTime();
        Long scanActualEndTime = tbCrmDetail.getScanActualEndTime();
        Long markPaperActualStartTime = tbCrmDetail.getMarkPaperActualStartTime();
        Long markPaperActualEndTime = tbCrmDetail.getMarkPaperActualEndTime();
        int scanPeriod = 0;
        int markPaperPeriod = 0;
        if (SystemConstant.longNotNull(scanActualStartTime) && SystemConstant.longNotNull(scanActualEndTime)) {
            scanPeriod = DateDisposeUtils.getDaysBetween(scanActualStartTime, scanActualEndTime, SystemConstant.DEFAULT_DATE_YMD_S_PATTERN).size();
        }
        if (SystemConstant.longNotNull(markPaperActualStartTime) && SystemConstant.longNotNull(
                markPaperActualEndTime)) {
            markPaperPeriod = DateDisposeUtils.getDaysBetween(markPaperActualStartTime, markPaperActualEndTime,
                    SystemConstant.DEFAULT_DATE_YMD_S_PATTERN).size();
        }
        SopPeriodDto sopPeriodDto = new SopPeriodDto();
        sopPeriodDto.setScanPeriod(scanPeriod);
        sopPeriodDto.setMarkPaperPeriod(markPaperPeriod);
        return sopPeriodDto;
    }
}
