package com.ximai.mes.cal.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ximai.common.utils.MessageUtils;
import com.ximai.common.utils.data.DateUtils;
import com.ximai.common.utils.data.ExceptionUtil;
import com.ximai.mes.cal.domain.CalPlan;
import com.ximai.mes.cal.domain.CalPlanWorkunit;
import com.ximai.mes.cal.domain.CalShift;
import com.ximai.mes.cal.domain.CalWorkunit;
import com.ximai.mes.cal.mapper.CalPlanMapper;
import com.ximai.mes.cal.mapper.CalPlanWorkunitMapper;
import com.ximai.mes.cal.mapper.CalShiftMapper;
import com.ximai.mes.cal.mapper.CalWorkunitMapper;
import com.ximai.mes.cal.service.ICalPlanService;
import com.ximai.mes.cal.service.ICalPlanWorkunitService;
import com.ximai.mes.cal.service.ICalShiftService;
import com.ximai.mes.cal.service.ICalWorkunitService;
import com.ximai.mes.cal.utils.MesCalendarUtil;
import com.ximai.mes.constant.CalPlanWorkDayEnum;
import com.ximai.mes.constant.CalPlanWorkunitEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

import static com.ximai.common.utils.data.DateUtils.formatShiftDate;
import static com.ximai.common.utils.data.DateUtils.getWeekendByMonth;
import static com.ximai.mes.cal.utils.MesCalendarUtil.addDays;

/**
 * 排班计划Service业务层处理
 *
 * @date 2022-06-06
 */
@Service
public class CalPlanServiceImpl implements ICalPlanService {
    @Autowired
    private CalPlanMapper calPlanMapper;


    @Autowired
    private CalShiftMapper calShiftMapper;

    @Autowired
    private ICalShiftService calShiftService;
    @Autowired
    private ICalPlanWorkunitService calPlanWorkunitService;

    @Autowired
    private CalPlanWorkunitMapper calPlanWorkunitMapper;
    @Autowired
    private ICalWorkunitService calWorkunitService;
    @Resource
    private CalWorkunitMapper calWorkunitMapper;

    /**
     * 查询排班计划
     *
     * @param planId 排班计划主键
     * @return 排班计划
     */
    @Override
    public CalPlan selectCalPlanByPlanId(Long planId) {
        return calPlanMapper.selectListByQw(new QueryWrapper<CalPlan>().eq("plan_id", planId)).get(0);
    }

    /**
     * 查询排班计划列表
     *
     * @param calPlan 排班计划
     * @return 排班计划
     */
    @Override
    public List<CalPlan> selectCalPlanList(CalPlan calPlan) {
        return calPlanMapper.selectCalPlanList(calPlan);
    }

    /**
     * 新增排班计划
     *
     * @param calPlan 排班计划
     * @return 结果
     */
    @Override
    public int insertCalPlan(CalPlan calPlan) {
        calPlan.setPlanId(null);
        calPlan.createAction();
        return calPlanMapper.insertCalPlan(calPlan);
    }

    @Override
    public void updateRelationship(CalPlan calPlan) {
        String planCode = calPlan.getPlanCode();

        List<CalShift> calShifts2 = new ArrayList<>();
        List<CalShift> calShifts = calPlan.getCalShifts();
        calShiftMapper.deleteByPlanCode(planCode);
        for (CalShift calShift : calShifts) {
            calShift = calPlan.deepSetObj(calShift);
            calShift.setShiftId(null);
            calShift.setManualUpdate(true);
            calShiftService.insertCalShift(calShift);
            if (CollectionUtil.isNotEmpty(calShift.getBreakTimeList())) {
                for (CalShift shift : calShift.getBreakTimeList()) {
                    shift = calPlan.deepSetObj(shift);
                    shift.setShiftId(null);
                    shift.setOrderNum(calShift.getOrderNum());
                    shift.setOperType(1);
                    shift.setShiftName(calShift.getShiftName());
                    shift.setManualUpdate(true);
                    calShiftService.insertCalShift(shift);
                }
            }
            calShifts2.add(calShift);
        }
        calPlan.setCalShifts(calShifts2);
    }

    /**
     * 修改排班计划
     *
     * @param calPlan 排班计划
     * @return 结果
     */
    @Override
    public int updateCalPlan(CalPlan calPlan) {
        calPlan.updateAction();
        return calPlanMapper.updateCalPlan(calPlan);
    }

    /**
     * 修改排班计划
     *
     * @param calPlan 排班计划
     * @return 结果
     */
    @Async
    @Transactional
    @Override
    public void releasePlan(CalPlan calPlan) {
        calPlanMapper.updateCalPlan(calPlan);
        this.updateRelationship(calPlan);
        List<CalWorkunit> calWorkunits = calWorkunitMapper.selectListByQw(new QueryWrapper<CalWorkunit>().eq("plan_id", calPlan.getPlanId()));
        ExceptionUtil.checkTrueThrowException(CollectionUtil.isEmpty(calWorkunits), MessageUtils.message("cal.error.error19"));
        List<CalPlanWorkunit> calPlanWorkunits = this.genRecords(calPlan, calWorkunits);
        this.saveCalPlanWorkunit(calPlan, calPlanWorkunits, calWorkunits);
    }

    /**
     * 批量删除排班计划
     *
     * @param planIds 需要删除的排班计划主键
     * @return 结果
     */
    @Override
    public int deleteCalPlanByPlanIds(Long[] planIds) {
        return calPlanMapper.deleteCalPlanByPlanIds(planIds);
    }

    /**
     * 删除排班计划信息
     *
     * @param planId 排班计划主键
     * @return 结果
     */
    @Override
    public int deleteCalPlanByPlanId(Long planId) {
        return calPlanMapper.deleteCalPlanByPlanId(planId);
    }

    /**
     * 根据排班计划生成每个班组的明细排班记录
     * 1.查询计划头
     * 2.查询计划中的班组
     * 3.查询计划中的班次
     * 4.计算计划的开始日期和结束日期的差值
     * 5.遍历每一天，设置每一天的班组与班次的对应关系（要结合轮班方式）
     *
     * @param
     * @param calWorkunits
     */
    private static List<CalPlanWorkunit> generateShiftRecords(List<CalShift> shifts, CalPlan plan, List<String> list, List<CalWorkunit> calWorkunits) {
        List<CalPlanWorkunit> saveList = new ArrayList<>();
        for (String nowDate : list) {
            for (CalWorkunit calWorkunit : calWorkunits) {
                int diffDay = 0;
                for (CalShift shift : shifts) {
                    CalPlanWorkunit calPlanWorkunit = new CalPlanWorkunit();
                    calPlanWorkunit.setPlanId(plan.getPlanId());
                    calPlanWorkunit.setShiftType(plan.getShiftType());
                    calPlanWorkunit.setShiftId(shift.getShiftId());
                    calPlanWorkunit.setShiftName(shift.getShiftName());
                    calPlanWorkunit.setIsWork(CalPlanWorkunitEnum.YES.getType());
                    calPlanWorkunit.setTheDay(nowDate);
                    calPlanWorkunit.setWorkunitId(calWorkunit.getWorkunitId());
                    calPlanWorkunit.setWorkunitCode(calWorkunit.getWorkunitCode());
                    calPlanWorkunit.setWorkunitName(calWorkunit.getWorkunitName());
                    Date date1 = MesCalendarUtil.dateStrToDate(nowDate + " " + MesCalendarUtil.getDateHourStr(shift.getStartDate()));
                    calPlanWorkunit.setStartDate(addDays(date1, diffDay));
                    Date date = MesCalendarUtil.dateStrToDate(nowDate + " " + MesCalendarUtil.getDateHourStr(shift.getEndDate()));
                    if (date1.getTime() > date.getTime()) {
                        diffDay++;
                    }
                    calPlanWorkunit.setEndDate(addDays(date, diffDay));
                    calPlanWorkunit.setRestFlag(shift.getRestFlag());
                    saveList.add(calPlanWorkunit);
                }
            }
        }
        return saveList;
    }


    /**
     * 根据排班计划生成每个班组的明细排班记录
     * 1.查询计划头
     * 2.查询计划中的班组
     * 3.查询计划中的班次
     * 4.计算计划的开始日期和结束日期的差值
     * 5.遍历每一天，设置每一天的班组与班次的对应关系（要结合轮班方式）
     *
     * @param calPlan
     */
    @Async
    @Override
    public List<CalPlanWorkunit> genRecords(CalPlan calPlan, List<CalWorkunit> calWorkunits) {
        List<CalShift> shifts = calPlan.getCalShifts();
        List<CalShift> shiftList = new ArrayList<>();
        Date startDate = calPlan.getStartDate();
        Date endDate = calPlan.getEndDate();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String startDateFormat = sdf.format(startDate);

        for (CalShift shift : shifts) {
            List<CalShift> breakTimeList = shift.getBreakTimeList();
            if (CollectionUtil.isNotEmpty(breakTimeList)) {
                breakTimeList = breakTimeList.stream().sorted(Comparator.comparing(CalShift::getOrderNum)).collect(Collectors.toList());
                Date shift1StartDate = formatShiftDate(startDateFormat, shift.getStartTime());
                Date shift1EndDate = formatShiftDate(startDateFormat, breakTimeList.get(0).getBreakStartTime());
                CalShift shift1 = shift.deepCopyObj();
                shift1.setStartDate(shift1StartDate);
                shift1.setEndDate(shift1EndDate);
                shiftList.add(shift1);

                for (CalShift calShift : breakTimeList) {
                    shift1StartDate = formatShiftDate(startDateFormat, calShift.getBreakStartTime());
                    shift1EndDate = formatShiftDate(startDateFormat, calShift.getBreakEndTime());
                    shift1 = new CalShift();
                    shift1.setStartDate(shift1StartDate);
                    shift1.setEndDate(shift1EndDate);
                    shift1.setOperType(1);
                    shift1.setRestFlag(1);
                    shift1.setShiftName(calShift.getShiftName());
                    shift1.setShiftId(calShift.getShiftId());
                    shiftList.add(shift1);
                }
                Date shift3EndDate = formatShiftDate(startDateFormat, shift.getEndTime());
                CalShift shift3 = shift.deepCopyObj();
                shift3.setStartDate(shift1EndDate);
                shift3.setEndDate(shift3EndDate);
                shiftList.add(shift3);

            } else {
                Date shift1StartDate = formatShiftDate(startDateFormat, shift.getStartTime());
                Date shift1EndDate = formatShiftDate(startDateFormat, shift.getEndTime());
                CalShift shift1 = shift.deepCopyObj();
                shift1.setStartDate(shift1StartDate);
                shift1.setEndDate(shift1EndDate);
                shiftList.add(shift1);
            }
        }
        List<String> weekendMonthStrList = this.getWeekendList(calPlan.getSaturday(), calPlan.getSunday(), calPlan.getStartDate(), calPlan.getEndDate());
        List<CalPlanWorkunit> calPlanWorkunits = generateShiftRecords(shiftList, calPlan, weekendMonthStrList, calWorkunits);
        calPlanWorkunits.forEach(x -> {
            String updateBy = calPlan.getUpdateBy();
            x.setCreateBy(updateBy);
            x.setUpdateBy(updateBy);
            Date nowDate = DateUtils.getNowDate();
            x.setCreateTime(nowDate);
            x.setUpdateTime(nowDate);
        });
        return calPlanWorkunits;
    }

    private void saveCalPlanWorkunit(CalPlan calPlan, List<CalPlanWorkunit> calPlanWorkunits, List<CalWorkunit> calWorkunits){
        List<String> shiftNames = calPlan.getCalShifts().stream().map(CalShift::getShiftName).collect(Collectors.toList());

        List<Long> taskWorkunitIds = calWorkunits.stream().map(CalWorkunit::getWorkunitId).collect(Collectors.toList());
        List<String> weekendMonthStrList = this.getWeekendList(calPlan.getSaturday(), calPlan.getSunday(), calPlan.getStartDate(), calPlan.getEndDate());
        // 删除同一天在同一个工作单元已经粗在上的旧数据
        QueryWrapper<CalPlanWorkunit> calPlanWorkunitQueryWrapper = new QueryWrapper<>();
        calPlanWorkunitQueryWrapper.in("t1.the_day", weekendMonthStrList);
        calPlanWorkunitQueryWrapper.in("t1.shift_name", shiftNames);
        calPlanWorkunitQueryWrapper.in("t1.workunit_id", taskWorkunitIds);
        List<Long> ids = calPlanWorkunitService.selectListByQw(calPlanWorkunitQueryWrapper).stream().map(CalPlanWorkunit::getId).collect(Collectors.toList());
        List<List<Long>> deletelists = chunkList(ids, 500);
        for (List<Long> list : deletelists) {
            EXECUTOR.execute(() -> calPlanWorkunitService.deleteCalPlanWorkunitByPlanWorkunitIds(list.toArray(new Long[]{})));
        }


        List<List<CalPlanWorkunit>> savelists = chunkList(calPlanWorkunits, 500);
        for (List<CalPlanWorkunit> list : savelists) {
            EXECUTOR.execute(() -> calPlanWorkunitMapper.insertBatchCalPlanWorkunits(list));
        }
    }

    private List<String> getWeekendList(int saturdayRest, int sundayRest, Date startDate, Date endDate){
        List<Integer> weekList = new ArrayList<>();
        if (Objects.equals(saturdayRest, CalPlanWorkDayEnum.WEEKEND.getType())) {
            weekList.add(Calendar.SATURDAY);
        }
        if (Objects.equals(sundayRest, CalPlanWorkDayEnum.WEEKEND.getType())) {
            weekList.add(Calendar.SUNDAY);
        }
        List<Date> weekendByMonth = getWeekendByMonth(startDate, endDate, weekList);
        List<String> weekendMonthStrList = weekendByMonth.stream().filter(Objects::nonNull).map(MesCalendarUtil::getDateDayStr).collect(Collectors.toList());
        return weekendMonthStrList;
    }

    public static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(4);


    public static <T> List<List<T>> chunkList(List<T> list, int chunkSize) {
        if (chunkSize <= 0) {
            throw new IllegalArgumentException("Chunk size must be greater than zero.");
        }
        List<List<T>> chunks = new ArrayList<>();
        for (int i = 0; i < list.size(); i += chunkSize) {
            chunks.add(list.subList(i, Math.min(i + chunkSize, list.size())));
        }
        return chunks;
    }

    @Override
    public void insertCalWorkunits(CalPlan calPlan) {
        calWorkunitService.deleteCalWorkunitByPlanCode(calPlan.getPlanCode());
        List<CalWorkunit> calWorkunits = calPlan.getCalWorkunits();
        for (CalWorkunit calWorkunit : calWorkunits) {
            calWorkunit = calPlan.deepSetObj(calWorkunit);
            calWorkunit.setManualUpdate(true);
            calWorkunit.setId(null);
            calWorkunitService.insertCalWorkunit(calWorkunit);
        }
    }
}
