package com.engine.jucailinkq.attendance.workflow.service.impl;

import com.engine.common.util.ServiceUtil;
import com.engine.core.impl.Service;
import com.engine.jucailinkq.attendance.attendanceanalysis.service.UtilService;
import com.engine.jucailinkq.attendance.attendanceanalysis.service.impl.UtilServiceImpl;
import com.engine.jucailinkq.attendance.enums.AttendanceItemTypeEnum;
import com.engine.jucailinkq.attendance.enums.ClassSegmentTypeEnum;
import com.engine.jucailinkq.attendance.enums.DateTypeEnum;
import com.engine.jucailinkq.attendance.enums.WorkForTimeEnum;
import com.engine.jucailinkq.attendance.workflow.service.OvertimePlanService;
import com.engine.jucailinkq.common.util.CommonUtil;
import com.engine.jucailinkq.common.util.DateUtil;
import com.engine.jucailinkq.common.util.DbTools;
import lombok.extern.slf4j.Slf4j;
import weaver.general.Util;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: sy
 * @Description: 加班计划流程相关方法实现类
 * @Date: 2024/7/18
 **/
@Slf4j
public class OvertimePlanServiceImpl extends Service implements OvertimePlanService {

    private UtilService utilService = ServiceUtil.getService(UtilServiceImpl.class);

    @Override
    public Map<String, Object> generateOvertimePlanDetails(Map<String, Object> params) {
        Map<String,Object> resultMap = new HashMap<>();
        List<String> errorMessage = new ArrayList<>();
        try {
            // 流程表单主表数据
            Map<String,String> mainTableData = (Map<String,String>)params.get("mainTableData");
            log.info("mainTableData : {}", mainTableData);
            //加班人员、开始日期、结束日期、开始时间、结束时间
            String jbry = Util.null2String(mainTableData.get("jbry"));
            String startDate = Util.null2String(mainTableData.get("ksrq"));
            String endDate = Util.null2String(mainTableData.get("jsrq"));
            String startTime = Util.null2String(mainTableData.get("kssj"));
            String endTime = Util.null2String(mainTableData.get("jssj"));
            //获取加班人员列表
            List<String> empIdList = "".equals(jbry) ? new ArrayList<>() : Arrays.asList(jbry.split(","));
            //获取加班日期集合
            List<String> dateList = DateUtil.getDatesBetween(startDate, endDate);
            //获取人员列表在日期区间的班次数据
            Map<String, List<Map<String, Object>>> scheduleInfoMap = utilService.getScheduleInfoWithEmpId(empIdList, DateUtil.beforeDay(startDate, 1), DateUtil.AfterDay(endDate,1));
            List<Map<String, String>> overtimePlanDetailList = new ArrayList<>();
            List<String> workBdlxList = new ArrayList<>();
            workBdlxList.add(ClassSegmentTypeEnum.WORK_TIME.getKey());workBdlxList.add(ClassSegmentTypeEnum.EXTENDED_OVERTIME.getKey());
            workBdlxList.add(ClassSegmentTypeEnum.EARLY_OVERTIME.getKey());workBdlxList.add(ClassSegmentTypeEnum.OVERTIME_IN_CLASS.getKey());
            Map<String, String> detailItem;
            String sql = "";
            //获取作用时段包含计划加班的加班类型的考勤项目集合
            sql = "select id,mc, bddrqlx, jbqsfzs,xzzjbsc,rzdjbxss,zzdjbxss,yzdjbxss from uf_jcl_kq_kqxm where xmlx = ? and zysd = ?";
            List<Map<String, Object>> jblxAttendanceList = DbTools.getSqlToList(sql, AttendanceItemTypeEnum.WORK_OVERTIME.getKey(), WorkForTimeEnum.PLAN_WORK_OVERTIME.getKey());
            //获取人员id和姓名信息
            Map<String, String> empIdToNameInfo = CommonUtil.empIdToNameInfo(empIdList);
            //获取目标人员列表已存在的加班计划
            Map<String, List<Map<String, Object>>> existOvertimePlanInfo = getOvertimePlanInfo(empIdList, startDate);
            List<Map<String, Object>> existOvertimePlanList = new ArrayList<>();

            for (String empId : empIdList) {
                List<Map<String, Object>> scheduleInfoList = scheduleInfoMap.get(empId);
                Map<String, String> dateToBcxxMap = scheduleInfoList == null ? new HashMap<>() : scheduleInfoList.stream().collect(Collectors.toMap(e->Util.null2String(e.get("bcrq")), e->Util.null2String(e.get("bcxx"))));
                Map<String, String> dateTypeInfoFromBc = scheduleInfoList == null ? new HashMap<>() : scheduleInfoList.stream().collect(Collectors.toMap(e->Util.null2String(e.get("bcrq")), e->Util.null2String(e.get("rqlx"))));
                existOvertimePlanList = existOvertimePlanInfo.get(empId);
                for (String date : dateList) {
                    String realEndDate = date;
                    detailItem = new HashMap<>();
                    detailItem.put("jbry", empId);
                    detailItem.put("jbryName", Util.null2String(empIdToNameInfo.get(empId)));
                    detailItem.put("ksrq", date);
                    detailItem.put("kssj", startTime);
                    detailItem.put("jssj", endTime);
                    detailItem.put("jblx", null);
                    detailItem.put("jblxName", null);
                    //开始时时和结束时间存在跨天情况时,即开始时间大于等于结束时间
                    if (startTime.compareTo(endTime) >= 0) {
                        realEndDate = DateUtil.AfterDay(date,1);
                    }
                    detailItem.put("jsrq", realEndDate);
                    //判断改组明细是否与已存在的加班计划有重叠
                    boolean overLappingSign = checkOverlapping(existOvertimePlanList, date + " " +startTime, realEndDate + " " + endTime);
                    if (overLappingSign) {
                        errorMessage.add(Util.null2String(empIdToNameInfo.get(empId)) + "在日期" + date + "的加班计划区间和已申请的加班计划（已审核/待审核）出现时间重叠，不允许申请加班！");
                        continue;
                    }
                    //加班时长
                    int overtimeMinutes = DateUtil.getBetWeenMinutes(date + " " + startTime, realEndDate + " " + endTime);
                    detailItem.put("jbsc", String.format("%.2f", overtimeMinutes / 60.0));
                    //根据班次数据，设置归属日期
                    //获取当天班次id
                    String currentDayBcId = Util.null2String(dateToBcxxMap.get(date)).split("-")[0];
                    //获取当天班次开始时间和结束时间
                    String currentDayStartToEnd = getBcStartAndEndTime(date, currentDayBcId, workBdlxList);
                    String currentDayStartTime = !"".equals(currentDayStartToEnd) ? currentDayStartToEnd.split(",")[0] : "";
                    String currentDayEndTime = !"".equals(currentDayStartToEnd) ? currentDayStartToEnd.split(",")[1] : "";
                    //判断当天班次是否为休息班次、且“休息班打卡自动加班”未勾选
                    boolean currentDayRestBc = checkRestBc(currentDayBcId);

                    //获取前1天班次id
                    String beforeDayBcId = Util.null2String(dateToBcxxMap.get(DateUtil.beforeDay(date, 1))).split("-")[0];
                    //获取前1天班次开始时间和结束时间
                    String beforeDayStartToEnd = getBcStartAndEndTime(DateUtil.beforeDay(date, 1), beforeDayBcId, workBdlxList);
                    String beforeDayStartTime = !"".equals(beforeDayStartToEnd) ? beforeDayStartToEnd.split(",")[0] : "";
                    String beforeDayEndTime = !"".equals(beforeDayStartToEnd) ? beforeDayStartToEnd.split(",")[1] : "";
                    //判断前1天班次是否为休息班次、且“休息班打卡自动加班”未勾选
                    boolean beforeDayRestBc = checkRestBc(beforeDayBcId);

                    //获取次日班次id
                    String nextDayBcId = Util.null2String(dateToBcxxMap.get(DateUtil.AfterDay(date, 1))).split("-")[0];
                    //获取次日班次开始时间和结束时间
                    String nextDayStartToEnd = getBcStartAndEndTime(DateUtil.AfterDay(date, 1), nextDayBcId, workBdlxList);
                    String nextDayStartTime = !"".equals(nextDayStartToEnd) ? nextDayStartToEnd.split(",")[0] : "";
                    String nextDayEndTime = !"".equals(nextDayStartToEnd) ? nextDayStartToEnd.split(",")[1] : "";
                    //判断次日班次是否为休息班次、且“休息班打卡自动加班”未勾选
                    boolean nextDayRestBc = checkRestBc(nextDayBcId);

                    //梳理加班时间段和班段时间段合理的情况
                    boolean beforeToCurrentDay = false;
                    boolean currentToNextDay = false;
                    boolean beforeToNextDay = false;
                    String belongDate = "";
                    if (!"".equals(currentDayStartToEnd)) {
                        //1-加班时间段处于前一日下班后和当日上班前
                        beforeToCurrentDay = ("".equals(beforeDayEndTime) || beforeDayEndTime.compareTo(date + " " + startTime) <= 0) && currentDayStartTime.compareTo(realEndDate + " " + endTime) >=0;
                        //2-加班时间段处于当日下班后和次日上班前
                        currentToNextDay = ("".equals(nextDayStartTime) || nextDayStartTime.compareTo(realEndDate + " " + endTime) >= 0) && currentDayEndTime.compareTo(date + " " + startTime) <=0;
                    } else {
                        //3-加班时间段处于前一日下班后和次日上班前
                        beforeToNextDay = ("".equals(beforeDayEndTime) || beforeDayEndTime.compareTo(date + " " + startTime) <= 0)
                                && ("".equals(nextDayStartTime) || nextDayStartTime.compareTo(realEndDate + " " + endTime) >= 0);
                    }
                    if (beforeToCurrentDay) {
                        int beforeDayDiffMinutes = !"".equals(beforeDayEndTime) ? DateUtil.getBetWeenMinutes(beforeDayEndTime, date + " " + startTime) : 9999;
                        int currentDayDiffMinutes = DateUtil.getBetWeenMinutes(realEndDate + " " + endTime, currentDayStartTime);
                        belongDate = currentDayDiffMinutes < beforeDayDiffMinutes ? date : DateUtil.beforeDay(date, 1);
                    } else if (currentToNextDay) {
                        int nextDayDiffMinutes = !"".equals(nextDayStartTime) ? DateUtil.getBetWeenMinutes(realEndDate + " " + endTime, nextDayStartTime) : 9999;
                        int currentDayDiffMinutes = DateUtil.getBetWeenMinutes(currentDayEndTime, date + " " + startTime);
                        belongDate = currentDayDiffMinutes <= nextDayDiffMinutes ? date : DateUtil.AfterDay(date, 1);
                    } else if (beforeToNextDay) {
                        int beforeDayDiffMinutes = !"".equals(beforeDayEndTime) ? DateUtil.getBetWeenMinutes(beforeDayEndTime, date + " " + startTime) : 9999;
                        int nextDayDiffMinutes = !"".equals(nextDayStartTime) ? DateUtil.getBetWeenMinutes(realEndDate + " " + endTime, nextDayStartTime) : 9999;
                        if (beforeDayDiffMinutes > 120 && nextDayDiffMinutes > 120) {
                            //当天没班次时，前后两天班次班段与加班区间相隔超过120分钟时，则归属日为请假开始日期
                            belongDate = date;
                        } else {
                            belongDate = beforeDayDiffMinutes <= nextDayDiffMinutes ? DateUtil.beforeDay(date, 1) : DateUtil.AfterDay(date, 1);
                        }
                    } else {
                        //20240812需求变更，休息班次，允许加班计划区间和班次班段区间有重叠
                        boolean currentDayOverLap = !"".equals(currentDayStartToEnd) && DateUtil.isOverlapping(date + " " + startTime, realEndDate + " " + endTime, currentDayStartTime, currentDayEndTime);
                        boolean beforeDayOverLap = !"".equals(beforeDayStartToEnd) && DateUtil.isOverlapping(date + " " + startTime, realEndDate + " " + endTime, beforeDayStartTime, beforeDayEndTime);
                        boolean nextDayOverLap = !"".equals(nextDayStartToEnd) && DateUtil.isOverlapping(date + " " + startTime, realEndDate + " " + endTime, nextDayStartTime, nextDayEndTime);
                        if ((!currentDayRestBc && currentDayOverLap) || (!beforeDayRestBc && beforeDayOverLap) || (!nextDayRestBc && nextDayOverLap)) {
                            //20240814需求变更。非休息班次，出现重叠时间，自动调整加班开始或结束时间，并返回提示
                            Map<String, String> adjustInfo = new HashMap<>();
                            adjustInfo.put("startWithAdjust", date + " " + startTime);
                            adjustInfo.put("endWithAdjust", realEndDate + " " + endTime);
                            //处理前一天
                            if(beforeDayOverLap) {
                                belongDate = DateUtil.beforeDay(date, 1);
                                adjustInfo = doAdjust(adjustInfo, beforeDayStartTime, beforeDayEndTime);
                            }
                            //处理当天
                            if(currentDayOverLap) {
                                belongDate = "".equals(belongDate) ? date : belongDate;
                                adjustInfo = doAdjust(adjustInfo, currentDayStartTime, currentDayEndTime);
                            }
                            //处理次日
                            if(nextDayOverLap) {
                                belongDate = "".equals(belongDate) ? DateUtil.AfterDay(date, 1) : belongDate;
                                adjustInfo = doAdjust(adjustInfo, nextDayStartTime, nextDayEndTime);
                            }
                            //重置加班开始和结束时间、加班时长
                            detailItem.put("ksrq", adjustInfo.get("startDate"));
                            detailItem.put("kssj", adjustInfo.get("startTime"));
                            detailItem.put("jssj", adjustInfo.get("endTime"));
                            detailItem.put("jsrq", adjustInfo.get("endDate"));
                            overtimeMinutes = DateUtil.getBetWeenMinutes(adjustInfo.get("startWithAdjust"), adjustInfo.get("endWithAdjust"));
                            detailItem.put("jbsc", String.format("%.2f", overtimeMinutes / 60.0));
                            //4-非休息班次，出现重叠时间，属于不合理加班安排，返回提示”已自动调整开始和结束时间点“
                            errorMessage.add(Util.null2String(empIdToNameInfo.get(empId)) + "在日期" + date + "的加班计划区间和非休息班次班段出现时间重叠，已自动调整开始和结束时间点！");

                            //5-加班区间和休息班次出现重叠，首次重叠的日期，即为归属日期
                        } else if (beforeDayRestBc && beforeDayOverLap) {
                            belongDate = DateUtil.beforeDay(date, 1);
                        } else if (currentDayRestBc && currentDayOverLap) {
                            belongDate = date;
                        } else if (nextDayRestBc && nextDayOverLap) {
                            belongDate = DateUtil.AfterDay(date, 1);
                        }

                    }
                    //判断归属日的日期类型,首先从排班的信息来获取
                    String belongDateType = dateTypeInfoFromBc.getOrDefault(belongDate, "");
                    //排班信息无法获取日期类型时，从日历信息获取
                    belongDateType = "".equals(belongDateType) ? CommonUtil.getRqlx(empId, belongDate) : belongDateType;
                    //排班和日历都无法获取时，默认为工作日
                    belongDateType = "".equals(belongDateType) ? DateTypeEnum.WORK_DAY.getKey() : belongDateType;

                    //通过人员id、归属日期、归属日期类型来自动匹配加班类型
                    //获取人员在归属日被设置的考勤项目
                    List<Map<String, Object>> attendanceItemSetList = CommonUtil.getAttendanceItemsByEmpIdDate(empId, belongDate);
                    List<String> kqxmSetIds = attendanceItemSetList.stream().map(f->f.get("keyid").toString()).collect(Collectors.toList());
                    Map<String, Object> matchItemInfo = new HashMap<>();
                    for (Map<String, Object> attendanceItemInfo : jblxAttendanceList) {
                        //判断该加班类型考勤项目是否被该人员使用，且考勤项目的绑定的日期类型是否包含归属日期类型
                        if (kqxmSetIds.contains(Util.null2String(attendanceItemInfo.get("id")))
                                && Util.null2String(attendanceItemInfo.get("bddrqlx")).contains(belongDateType)) {
                            detailItem.put("jblx", Util.null2String(attendanceItemInfo.get("id")));
                            detailItem.put("jblxName", Util.null2String(attendanceItemInfo.get("mc")));
                            matchItemInfo = attendanceItemInfo;
                        }
                    }
                    detailItem.put("gsrq", belongDate);
                    //20240814需求变更。增加单条明细的最小加班分钟数校验，工作日加班最大小时数、周加班最大小时数、月加班最大小时数
                    if (matchItemInfo.size() == 0) {
                        errorMessage.add(Util.null2String(empIdToNameInfo.get(empId)) + "在日期" + date + "的加班计划未匹配到合适的加班类型！");
                        continue;
                    } else {
                        String minMinutes = Util.null2String(matchItemInfo.get("jbqsfzs"));
                        if (!"".equals(minMinutes) && Integer.parseInt(minMinutes) > overtimeMinutes) {
                            //最小加班分钟数大于单条明细的加班时长分钟数
                            errorMessage.add(Util.null2String(empIdToNameInfo.get(empId)) + "在日期" + date + "的加班分钟数小于加班类型-"
                                    + Util.null2String(matchItemInfo.get("mc")) +"设置的最小加班分钟数！");
                            continue;
                        }
                        String limitTotalOvertimeSc = Util.null2String(matchItemInfo.get("xzzjbsc"));
                        if ("1".equals(limitTotalOvertimeSc)) {
                            String limitWorkDayHours = Util.null2String(matchItemInfo.get("rzdjbxss"));
                            String limitWeekHours = Util.null2String(matchItemInfo.get("zzdjbxss"));
                            String limitMonthHours = Util.null2String(matchItemInfo.get("yzdjbxss"));
                            //判断是否满足工作日加班最大小时数
                            boolean needCheckWorkDayHours = !"".equals(limitWorkDayHours) && (belongDateType.equals(DateTypeEnum.WORK_DAY.getKey()) || belongDateType.equals(DateTypeEnum.CHANGECLASS.getKey()));
                            if (needCheckWorkDayHours && Double.compare(Double.parseDouble(limitWorkDayHours), overtimeMinutes / 60.0) < 0) {
                                errorMessage.add(Util.null2String(empIdToNameInfo.get(empId)) + "在日期" + date + "的加班时长加班类型-"
                                        + Util.null2String(matchItemInfo.get("mc")) +"设置的工作日加班最大小时数！");
                                continue;
                            }
                            //判断是否满足周加班最大小时数
                            if (!"".equals(limitWeekHours)) {

                            }
                            //判断是否满足月加班最大小时数
                            if (!"".equals(limitMonthHours)) {

                            }
                        }
                    }
                    overtimePlanDetailList.add(detailItem);
                }
            }
            if (errorMessage.size() == 0) {
                resultMap.put("status", true);
                resultMap.put("data", overtimePlanDetailList);
            } else {
                resultMap.put("status", false);
                resultMap.put("errorInfo", errorMessage);
                resultMap.put("data", null);
            }

        } catch (Exception e) {
            log.info(e.getMessage());
            resultMap.put("status", false);
            resultMap.put("errorInfo", e.getMessage());
            resultMap.put("data", null);
        }
        return resultMap;
    }

    private Map<String, String> doAdjust(Map<String, String> adjustInfo, String bcStartTime, String bcEndTime) {
        String startWithAdjust = adjustInfo.get("startWithAdjust");
        String endWithAdjust = adjustInfo.get("endWithAdjust");
        //加班区间完全包含了班次区间时
        if (bcStartTime.compareTo(startWithAdjust) > 0  && bcEndTime.compareTo(endWithAdjust) < 0) {
            startWithAdjust = bcEndTime;
        } else {
            //加班区间与部分班次区间重叠时
            startWithAdjust = bcStartTime.compareTo(startWithAdjust) <= 0 && bcEndTime.compareTo(startWithAdjust) > 0
                    ? bcEndTime : startWithAdjust;
            endWithAdjust = bcStartTime.compareTo(endWithAdjust) < 0 && bcEndTime.compareTo(endWithAdjust) >= 0
                    ? bcStartTime : endWithAdjust;
        }
        adjustInfo.put("startWithAdjust", startWithAdjust);
        adjustInfo.put("endWithAdjust", endWithAdjust);
        adjustInfo.put("startDate", startWithAdjust.split(" ")[0]);
        adjustInfo.put("startTime", startWithAdjust.split(" ")[1]);
        adjustInfo.put("endDate", endWithAdjust.split(" ")[0]);
        adjustInfo.put("endTime", endWithAdjust.split(" ")[1]);
        return adjustInfo;
    }

    private boolean checkRestBc(String bcId) {
        boolean restSign = false;
        if (!"".equals(bcId)) {
            //查询当天班次明细
            String sql = "select id, sfxx, xxbdkzdjb from uf_jcl_kq_bcxx where id = " + bcId;
            Map<String, Object> data = DbTools.getSqlToMap(sql);
            if ("1".equals(Util.null2String(data.get("sfxx"))) && !"1".equals(Util.null2String(data.get("xxbdkzdjb")))) {
                restSign = true;
            }
        }
        return restSign;
    }

    private boolean checkOverlapping(List<Map<String, Object>> existOvertimePlanList, String startTimePoint, String endTimePoint) {
        boolean overlappingSign = false;
        if (existOvertimePlanList != null && existOvertimePlanList.size() > 0) {
            String contrastStartPoint = "";
            String contrastEndPoint = "";
            for (Map<String, Object> item : existOvertimePlanList) {
                contrastStartPoint = item.get("ksrq") + " " + item.get("kssj");
                contrastEndPoint = item.get("jsrq") + " " + item.get("jssj");
                overlappingSign = DateUtil.isOverlapping(contrastStartPoint, contrastEndPoint, startTimePoint, endTimePoint);
                if (overlappingSign) {
                    break;
                }
            }
        }
        return overlappingSign;
    }

    /**
     * 获取目标人员在加班计划表中结束日期大于等于匹配日期的数据记录
     * @param empIdList 人员id列表
     * @param matchDate 匹配日期
     * @returnd
     */
    private Map<String, List<Map<String, Object>>> getOvertimePlanInfo(List<String> empIdList, String matchDate) {
        Map<String, List<Map<String, Object>>> result = new HashMap<>();
        if (empIdList.size() > 0 && !"".equals(matchDate)) {
            String sql = "select b.id, b.jbry, b.ksrq, b.jsrq, b.kssj, b.jssj from uf_jcl_kq_jbjh a left join uf_jcl_kq_jbjh_dt1 b on b.mainid = a.id where a.jlzt != 2 and b.jsrq >= '"
                    + matchDate + "' and b.jbry in (" + String.join(",",empIdList) + ")";
            List<Map<String, Object>> data = DbTools.getSqlToList(sql);
            result = data.stream().collect(Collectors.groupingBy(e->e.get("jbry").toString()));
        }
        return result;
    }

    public String getBcStartAndEndTime(String date, String currentDayBcId, List<String> workBdlxList) {
        String startToEnd = "";
        if (!"".equals(currentDayBcId)) {
            //查询当天班次明细
            String sql = "select id, bdlx, gsrq, kssj, jssj from uf_jcl_kq_bcxx_dt1 where mainid = " + currentDayBcId + " order by gsrq desc, kssj desc";
            List<Map<String, Object>> bcDetailData = DbTools.getSqlToList(sql);
            bcDetailData = bcDetailData.stream().filter(e -> workBdlxList.contains(Util.null2String(e.get("bdlx")))).collect(Collectors.toList());
            if (bcDetailData.size() > 0) {
                String endGsrqValue = Util.null2String(bcDetailData.get(0).get("gsrq"));
                String lastJssj = Util.null2String(bcDetailData.get(0).get("jssj"));
                String lastKssj = Util.null2String(bcDetailData.get(0).get("kssj"));
                String bdEndDate = "2".equals(endGsrqValue) ? DateUtil.AfterDay(date,1) : ("0".equals(endGsrqValue) ? DateUtil.beforeDay(date,1) : date);
                bdEndDate = lastKssj.compareTo(lastJssj) >= 0 ? DateUtil.AfterDay(bdEndDate,1) : bdEndDate;

                String startGsrqValue = Util.null2String(bcDetailData.get(bcDetailData.size() - 1).get("gsrq"));
                String firstKssj = Util.null2String(bcDetailData.get(bcDetailData.size() - 1).get("kssj"));
                String bdStartDate = "2".equals(startGsrqValue) ? DateUtil.AfterDay(date,1) : ("0".equals(startGsrqValue) ? DateUtil.beforeDay(date,1) : date);

                startToEnd = bdStartDate + " " + firstKssj + "," + bdEndDate + " " + lastJssj;
            }
        }
        return startToEnd;
    }
}
