package cn.iocoder.yudao.module.system.service.attendance.scheduling;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.Constants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.api.attendance.dto.AttendanceTimeRangeInfoDTO;
import cn.iocoder.yudao.module.system.api.attendance.vo.AttendanceGroupShiftItemVO;
import cn.iocoder.yudao.module.system.api.attendance.vo.AttendanceTimeRangeInfoVO;
import cn.iocoder.yudao.module.system.controller.admin.attendance.dto.AttendancePunchPageDTO;
import cn.iocoder.yudao.module.system.controller.admin.attendance.vo.AttendancePunchPageVO;
import cn.iocoder.yudao.module.system.controller.admin.attendance.vo.AttendanceRulesVO;
import cn.iocoder.yudao.module.system.controller.admin.groupshift.vo.AttendanceGroupShiftVO;
import cn.iocoder.yudao.module.system.controller.admin.scheduling.vo.AttendanceSchedulingPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.scheduling.vo.AttendanceSchedulingRespVO;
import cn.iocoder.yudao.module.system.controller.admin.scheduling.vo.AttendanceSchedulingSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.group.AttendanceGroupDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.groupshiftitem.AttendanceGroupShiftItemDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.scheduling.AttendanceSchedulingDO;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.scheduling.AttendanceSchedulingMapper;
import cn.iocoder.yudao.module.system.service.attendance.AttendanceService;
import cn.iocoder.yudao.module.system.service.attendance.groupshift.AttendanceGroupShiftService;
import cn.iocoder.yudao.module.system.service.attendance.groupshiftitem.AttendanceGroupShiftItemService;
import cn.iocoder.yudao.module.system.service.attendance.punch.PunchService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SCHEDULING_NOT_EXISTS;

/**
 * 排班制考勤设置 Service 实现类
 *
 * @author 艾楷
 */
@Service("scheduling")
@Validated
public class AttendanceSchedulingServiceImpl implements AttendanceSchedulingService, PunchService {

    @Resource
    private AttendanceSchedulingMapper schedulingMapper;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private AttendanceGroupShiftService attendanceGroupShiftService;
    @Resource
    @Lazy
    private AttendanceService attendanceService;
    @Resource
    private AttendanceGroupShiftItemService attendanceGroupShiftItemService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long batchCreateOrUpdate(Long attendanceGroupId, List<AttendanceSchedulingSaveReqVO> createReqVO) {
        Long groupId = null;
        List<AttendanceSchedulingDO> oldList = schedulingMapper.selectList(new LambdaQueryWrapper<AttendanceSchedulingDO>().eq(AttendanceSchedulingDO::getAttendanceGroupId, attendanceGroupId));
        List<AttendanceSchedulingDO> scheduling = BeanUtils.toBean(createReqVO, AttendanceSchedulingDO.class);
        // -- 如果久的没数据 则直接新增
        if (oldList.isEmpty()) {
            groupId = attendanceGroupId;
            schedulingMapper.insertBatch(scheduling);
        } else {
            //如果久的有数据则区分下
            // 插入
            List<AttendanceSchedulingDO> saveList = scheduling.stream().filter(a -> a.getId() == null).collect(Collectors.toList());
            List<AttendanceSchedulingDO> editList = scheduling.stream().filter(a -> a.getId() != null).collect(Collectors.toList());
            if (!saveList.isEmpty()) {
                groupId = attendanceGroupId;
                schedulingMapper.insertBatch(saveList);
            }
            //获取到当天是第几天
            String schedulingKey = Constants.SCHEDULING + Constants.UNDERLINE + attendanceGroupId;
            String indexDayObj = stringRedisTemplate.opsForValue().get(schedulingKey);
            if (!editList.isEmpty()) {
                Map<Integer, AttendanceSchedulingDO> map = oldList.stream().collect(Collectors.toMap(AttendanceSchedulingDO::getIndexDay, a -> a));
                for (AttendanceSchedulingDO attendanceSchedulingDO : editList) {
                    AttendanceSchedulingDO item = map.get(attendanceSchedulingDO.getIndexDay());
                    boolean eqFlag = BeanUtil.isCommonFieldsEqual(attendanceSchedulingDO, item, "createTime", "updateTime", "creator", "updater", "deleted");
                    // -- 判断当天是修改的数据是否是当天 - 如果是的话则需要更新
                    if (!eqFlag && (StrUtil.isNotEmpty(indexDayObj) && Integer.parseInt(indexDayObj) == attendanceSchedulingDO.getIndexDay())) {
                        groupId = attendanceGroupId;
                        break;
                    }
                }
                schedulingMapper.updateBatch(editList);
            }
            // -- 需要删除的 -
            List<Long> oldIds = oldList.stream().map(AttendanceSchedulingDO::getId).collect(Collectors.toList());
            List<Long> newIds = editList.stream().map(AttendanceSchedulingDO::getId).collect(Collectors.toList());
            List<Long> delIds = new ArrayList<>(CollectionUtil.subtract(oldIds, newIds));
            if (!delIds.isEmpty()) {
                schedulingMapper.deleteBatchIds(delIds);
                groupId = attendanceGroupId;
            }
        }
        return groupId;
    }

    @Override
    public void updateScheduling(AttendanceSchedulingSaveReqVO updateReqVO) {
        // 校验存在
        validateSchedulingExists(updateReqVO.getId());
        // 更新
        AttendanceSchedulingDO updateObj = BeanUtils.toBean(updateReqVO, AttendanceSchedulingDO.class);
        schedulingMapper.updateById(updateObj);
    }

    @Override
    public void deleteScheduling(Long id) {
        // 校验存在
        validateSchedulingExists(id);
        // 删除
        schedulingMapper.deleteById(id);
    }

    private void validateSchedulingExists(Long id) {
        if (schedulingMapper.selectById(id) == null) {
            throw exception(SCHEDULING_NOT_EXISTS);
        }
    }

    @Override
    public AttendanceSchedulingDO getScheduling(Long id) {
        return schedulingMapper.selectById(id);
    }

    @Override
    public PageResult<AttendanceSchedulingDO> getSchedulingPage(AttendanceSchedulingPageReqVO pageReqVO) {
        return schedulingMapper.selectPage(pageReqVO);
    }

    @Override
    public AttendanceSchedulingDO getSchedulingByIndexDay(Long attendanceGroupId, Integer indexDay) {
        List<AttendanceSchedulingDO> list = schedulingMapper.selectList(new LambdaQueryWrapper<AttendanceSchedulingDO>()
                .eq(AttendanceSchedulingDO::getAttendanceGroupId, attendanceGroupId)
                .eq(AttendanceSchedulingDO::getIndexDay, indexDay));
        if (!list.isEmpty()) {
            return list.get(0);
        }
        return null;
    }

    @Override
    public List<AttendanceSchedulingDO> getSchedulingByGroupId(Long attendanceGroupId) {
        return schedulingMapper.selectList(new LambdaQueryWrapper<AttendanceSchedulingDO>()
                .eq(AttendanceSchedulingDO::getAttendanceGroupId, attendanceGroupId));
    }

    @Override
    public Integer getCycleDay(Long attendanceGroupId) {
        return Math.toIntExact(schedulingMapper.selectCount(new LambdaQueryWrapper<AttendanceSchedulingDO>()
                .eq(AttendanceSchedulingDO::getAttendanceGroupShiftId, attendanceGroupId)));
    }

    @Override
    public Map<Long, Map<String, Object>> countGroupId() {
        return schedulingMapper.countGroupId();
    }

    @Override
    public Map<Long, Long> getGroupToShiftIdMap(List<AttendanceGroupDO> schedulingList, LocalDateTime localDateTime) {
        Map<Long, Long> map = new HashMap<>();
        for (AttendanceGroupDO activationGroup : schedulingList) {
            //获取到当天是第几天
            String schedulingKey = Constants.SCHEDULING + Constants.UNDERLINE + activationGroup.getId();
            String indexDayObj = stringRedisTemplate.opsForValue().get(schedulingKey);
            if (StrUtil.isEmpty(indexDayObj)) {
                // 当作第一天来
                indexDayObj = Constants.ONE_STR;
                stringRedisTemplate.opsForValue().set(schedulingKey, indexDayObj);
            }
            Integer indexDay = Integer.valueOf(indexDayObj);
            AttendanceSchedulingDO attendanceSchedulingDO = this.getSchedulingByIndexDay(activationGroup.getId(), indexDay);
            // -- 当前没有班次 - 不需要考勤
            if (attendanceSchedulingDO == null || Constants.ONE.equals(attendanceSchedulingDO.getRestFlag()) || attendanceSchedulingDO.getAttendanceGroupShiftId() == null) {
                continue;
            }
            map.put(activationGroup.getId(), attendanceSchedulingDO.getAttendanceGroupShiftId());
        }
        return map;
    }


    @Override
    public List<AttendanceSchedulingRespVO> getListByGroupId(Long attendanceGroupId) {
        List<AttendanceSchedulingRespVO> respVOS = new ArrayList<>();
        List<AttendanceSchedulingDO> selectedList = schedulingMapper.selectList(new LambdaQueryWrapper<AttendanceSchedulingDO>()
                .eq(attendanceGroupId != null, AttendanceSchedulingDO::getAttendanceGroupId, attendanceGroupId));
        if (!selectedList.isEmpty()) {
            respVOS = BeanUtils.toBean(selectedList, AttendanceSchedulingRespVO.class);
            List<Long> attendanceGroupShiftIds = selectedList.stream().map(AttendanceSchedulingDO::getAttendanceGroupShiftId).filter(Objects::nonNull).collect(Collectors.toList());
            if (CollUtil.isEmpty(attendanceGroupShiftIds)) {
                return respVOS;
            }
            List<AttendanceGroupShiftVO> vos = attendanceGroupShiftService.listByShiftIds(attendanceGroupShiftIds);
            Map<Long, AttendanceGroupShiftVO> map = vos.stream().collect(Collectors.toMap(AttendanceGroupShiftVO::getId, a -> a));
            for (AttendanceSchedulingRespVO respVO : respVOS) {
                respVO.setShiftVO(map.get(respVO.getAttendanceGroupShiftId()));
            }
        }
        return respVOS;
    }

    @Override
    public List<Long> getUseGroupIds(Long shiftId) {
        return schedulingMapper.getUseGroupIds(shiftId);
    }

    @Override
    public AttendancePunchPageVO getPunchPage(AttendancePunchPageDTO dto) {
        AttendancePunchPageVO vo = new AttendancePunchPageVO();
        AttendanceGroupDO activationGroup = dto.getActivationGroup();
        vo.setActivationGroup(activationGroup);
        //获取到当天是第几天
        Integer indexDay = this.schedulingAttendanceIndexDayByGroupId(activationGroup.getId());
        AttendanceSchedulingDO attendanceSchedulingDO = this.getSchedulingByIndexDay(activationGroup.getId(), indexDay);
        // -- 当前没有班次 - 不需要考勤
        if (attendanceSchedulingDO == null || Constants.ONE.equals(attendanceSchedulingDO.getRestFlag()) || attendanceSchedulingDO.getAttendanceGroupShiftId() == null) {
            attendanceSchedulingDO = this.getSchedulingByIndexDay(activationGroup.getId(), -1);
            if (attendanceSchedulingDO == null || Constants.ONE.equals(attendanceSchedulingDO.getRestFlag()) || attendanceSchedulingDO.getAttendanceGroupShiftId() == null) {
                return vo.setTodayNeedAttendance(Constants.FALSE);
            } else {
                vo.setWorkOvertimeFlag(Constants.ONE);
            }
        }
        vo.setAttendanceGroupShiftId(attendanceSchedulingDO.getAttendanceGroupShiftId());
        attendanceService.calculatePunch(dto, vo);
        vo.setAttendanceGroupId(activationGroup.getId());
        vo.setUser(dto.getUser());
        return vo;
    }

    /**
     * 通过groupId获取到当天是第几天
     *
     * @param groupId
     * @return
     */
    private Integer schedulingAttendanceIndexDayByGroupId(Long groupId) {
        String schedulingKey = Constants.SCHEDULING + Constants.UNDERLINE + groupId;
        String indexDayObj = stringRedisTemplate.opsForValue().get(schedulingKey);
        if (StrUtil.isEmpty(indexDayObj)) {
            // 当作第一天来
            indexDayObj = Constants.ONE_STR;
        }
        return Integer.valueOf(indexDayObj);
    }


    @Override
    public List<AttendanceRulesVO.WorkRules> getRule(Long attendanceGroupId) {
        List<AttendanceSchedulingDO> list = this.getSchedulingByGroupId(attendanceGroupId);

        List<AttendanceRulesVO.WorkRules> rules = new ArrayList<>();
        List<Long> attendanceGroupShiftIds = list.stream().map(AttendanceSchedulingDO::getAttendanceGroupShiftId).filter(Objects::nonNull).collect(Collectors.toList());
        if (CollUtil.isEmpty(attendanceGroupShiftIds)) {
            return rules;
        }
        List<AttendanceGroupShiftVO> vos = attendanceGroupShiftService.listByShiftIds(attendanceGroupShiftIds);
        Map<Long, AttendanceGroupShiftVO> map = vos.stream().collect(Collectors.toMap(AttendanceGroupShiftVO::getId, a -> a));
        //获取到当天是第几天
        String schedulingKey = Constants.SCHEDULING + Constants.UNDERLINE + attendanceGroupId;
        String indexDayObj = stringRedisTemplate.opsForValue().get(schedulingKey);

        for (AttendanceSchedulingDO attendanceSchedulingDO : list) {
            AttendanceRulesVO.WorkRules workRules = new AttendanceRulesVO.WorkRules();
            String indexDay = "第" + attendanceSchedulingDO.getIndexDay() + "天";
            workRules.setIndexStr(indexDay);
            workRules.setFlag(false);
            if (StrUtil.isNotEmpty(indexDayObj) && Integer.parseInt(indexDayObj) == attendanceSchedulingDO.getIndexDay()) {
                workRules.setFlag(true);
            }
            if (attendanceSchedulingDO.getAttendanceGroupShiftId() == null) {
                workRules.setRule("休息");
            } else {
                AttendanceGroupShiftVO attendanceGroupShiftVOS = map.get(attendanceSchedulingDO.getAttendanceGroupShiftId());
                List<String> itemRule = new ArrayList<>();
                for (AttendanceGroupShiftItemDO attendanceGroupShiftItemDO : attendanceGroupShiftVOS.getAttendanceGroupShiftItemDOList()) {
                    itemRule.add(attendanceGroupShiftItemDO.getBeginTime() + "-" + attendanceGroupShiftItemDO.getEndTime());
                }
                String rule = String.join(",", itemRule);
                workRules.setRule(rule);
            }
            rules.add(workRules);
        }
        return rules;
    }


    @Override
    public Map<String, AttendanceTimeRangeInfoVO> getAttendanceInfoByTimeRange(AttendanceTimeRangeInfoDTO dto) {
        Map<String, AttendanceTimeRangeInfoVO> map = new HashMap<>();
        // -- 获取到当前考勤组在第几天 - 完了后再计算下 请假开始时间离当天有多少天 - 然后计算出请假的时间是第几天 再拿到对应天的考勤班次
        Integer indexDay = this.schedulingAttendanceIndexDayByGroupId(dto.getGroupId());
        LocalDateTime today = LocalDateTime.now();

        // -- 获取到请假开始时间离当天有多少天 - 先将两个时间转为一天的开始时间 这样计算准确
        long betweenDayNum = LocalDateTimeUtil.between(LocalDateTimeUtil.beginOfDay(today), LocalDateTimeUtil.beginOfDay(dto.getStartTime()), ChronoUnit.DAYS);
        // -- 再获取到一共有多少天再循环
        Long dayNum = schedulingMapper.selectCount(new LambdaQueryWrapper<AttendanceSchedulingDO>().eq(AttendanceSchedulingDO::getAttendanceGroupId, dto.getGroupId()));

        // -- 获取到请假开始时间离当天有多少天
        long l = indexDay + betweenDayNum;
        if (l > dayNum) {
            l = (l - dayNum) % dayNum == 0 ? dayNum : (l - dayNum) % dayNum;
        } else if (l < 1) {
            l = l % dayNum + dayNum;
        }
        // -- 开始请假天
        int beginTimeDay = (int) l;
        //不在考勤组
        for (String time : dto.getTimes()) {
            // -- 如果超出最大天则按第一天开始算
            if (beginTimeDay > dayNum) {
                beginTimeDay = 1;
            }
            AttendanceTimeRangeInfoVO attendanceTimeRangeInfoVO = new AttendanceTimeRangeInfoVO();
            // -- 判断是否根据节假日自动排班 - 如果是的话 - 根据排班的来
            LocalDateTime localDateTime = LocalDateTimeUtil.parseDate(time, Constants.REPO_DATE_FORMAT).atStartOfDay();
            Boolean holiday = attendanceService.isHoliday(localDateTime);
            Boolean isHolidayFlag = Constants.TRUE.equals(dto.getAutoHolidaysFlag()) ?
                    holiday : null;
            // -- 如果是节假日的话先插入下是节假日 - 不管上不上班
            attendanceTimeRangeInfoVO.setDatType(isHolidayFlag != null && isHolidayFlag ? Constants.THREE : Constants.ONE);
            // -- 当前是节假日 并且是放假
            if (isHolidayFlag != null && isHolidayFlag) {
                map.put(time, attendanceTimeRangeInfoVO);
                continue;
            }
            AttendanceSchedulingDO attendanceSchedulingDO = this.getSchedulingByIndexDay(dto.getGroupId(), beginTimeDay);
            // -- 当前没有班次 - 不需要考勤
            if (attendanceSchedulingDO == null || Constants.ONE.equals(attendanceSchedulingDO.getRestFlag()) || attendanceSchedulingDO.getAttendanceGroupShiftId() == null) {
                map.put(time, attendanceTimeRangeInfoVO.setDatType(Constants.TWO));
                continue;
            }
            List<AttendanceGroupShiftItemDO> attendanceGroupShiftItemDOList = attendanceGroupShiftItemService.getGroupShiftItemListByShiftId(attendanceSchedulingDO.getAttendanceGroupShiftId());
            if (CollectionUtil.isNotEmpty(attendanceGroupShiftItemDOList)) {
                List<AttendanceGroupShiftItemVO> items = BeanUtil.copyToList(attendanceGroupShiftItemDOList, AttendanceGroupShiftItemVO.class);
                map.put(time, attendanceTimeRangeInfoVO.setNeedAttendance(1).setItems(items));
            }
            beginTimeDay++;
        }
        return map;
    }

//    public static void main(String[] args) {
//        LocalDateTime today = LocalDateTime.of(2024, 10, 1, 0, 0, 0);
//        LocalDateTime start = LocalDateTime.of(2024, 10, 21, 0, 0, 0);
//        // -- 获取到请假开始时间离当天有多少天
//        long betweenDayNum = LocalDateTimeUtil.between(today, start, ChronoUnit.DAYS);
//        // -- 再获取到一共有多少天再循环
//        long dayNum = 10L;
//        int indexDay = 1;
//        long l = indexDay + betweenDayNum;
//        if (l > dayNum) {
//            l = (l - dayNum) % dayNum == 0 ? dayNum : (l - dayNum) % dayNum;
//        } else if (l < 1) {
//            l = l % dayNum + dayNum;
//        }
//        // -- 开始请假天
//        int beginTimeDay = (int) l;
//        System.out.println(beginTimeDay);
//    }
}
