package jnpf.attendance.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import jnpf.attendance.mapper.FtbAttendanceLineSchedulingConfigMapper;
import jnpf.attendance.service.AttendanceDailyRuleService;
import jnpf.attendance.service.AttendanceGroupService;
import jnpf.attendance.service.AttendanceLineSchedulingConfigService;
import jnpf.attendance.service.AttendanceUserService;
import jnpf.attendance.service.handle.notice.AttendanceNoticeHandler;
import jnpf.authority.FtbAuthorityApi;
import jnpf.base.ActionResult;
import jnpf.base.ModuleApi;
import jnpf.base.SystemApi;
import jnpf.base.entity.ModuleEntity;
import jnpf.base.entity.SystemEntity;
import jnpf.base.service.SuperServiceImpl;
import jnpf.base.vo.PageListVO;
import jnpf.entity.AttendanceGroup;
import jnpf.entity.AttendanceGroupUser;
import jnpf.entity.attendance.FtbAttendanceDailyRule;
import jnpf.entity.attendance.FtbAttendanceLineSchedulingConfig;
import jnpf.enums.attendance.AttendanceNoticeEnum;
import jnpf.model.attendance.dto.LineDrawingPeriodDto;
import jnpf.model.attendance.model.AttendanceGroupParam;
import jnpf.model.attendance.model.LineShiftChangeNoticeModel;
import jnpf.model.attendance.vo.AttendanceGroupUserVo;
import jnpf.model.personnels.dto.staff.roster.FtbPersonnelsStaffRosterDto;
import jnpf.model.personnels.req.roster.StaffRosterListReq;
import jnpf.permission.V2PositionApi;
import jnpf.permission.V2UserApi;
import jnpf.permission.dto.v2.position.QueryPositionUserListDTO;
import jnpf.permission.vo.v2.position.PositionListUserVO;
import jnpf.permission.vo.v2.user.UserBoundVO;
import jnpf.personnels.FtbPersonnelsRosterManagerApi;
import jnpf.util.StringUtil;
import jnpf.util.UploaderUtil;
import jnpf.util.UserProvider;
import org.apache.commons.compress.utils.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

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

import static jnpf.constants.RedisConstant.ATTENDANCE_LINE_SCHEDULING_NOTICE;

/**
 * 划线排班配置Service实现
 *
 * @author ahua
 * @version 2.1
 * @copyright 引迈信息技术有限公司（https://www.jnpfsoft.com）
 * @date 2025-12-30
 */
@Service
public class AttendanceLineSchedulingConfigServiceImpl extends SuperServiceImpl<FtbAttendanceLineSchedulingConfigMapper, FtbAttendanceLineSchedulingConfig> implements AttendanceLineSchedulingConfigService {

    @Resource
    private FtbAttendanceLineSchedulingConfigMapper ftbAttendanceLineSchedulingConfigMapper;
    @Autowired
    private AttendanceUserService attendanceGroupUserService;
    @Autowired
    private AttendanceGroupService attendanceGroupService;
    @Autowired
    private FtbPersonnelsRosterManagerApi ftbPersonnelsRosterManagerApi;
    @Autowired
    private AttendanceDailyRuleService attendanceDailyRuleService;
    @Autowired
    private AttendanceNoticeHandler attendanceNoticeHandler;
    @Autowired
    private FtbAuthorityApi ftbAuthorityApi;
    @Autowired
    private ModuleApi moduleApi;
    @Autowired
    private SystemApi systemApi;
    @Autowired
    private V2UserApi v2UserApi;
    @Autowired
    private V2PositionApi v2PositionApi;
    @Autowired
    private StringRedisTemplate redisTemplate;
    /**
     * 根据考勤组ID查询划线排班配置
     *
     * @param groupId 考勤组ID
     * @return 划线排班配置
     */
    @Override
    public FtbAttendanceLineSchedulingConfig getByGroupId(String groupId) {
        QueryWrapper<FtbAttendanceLineSchedulingConfig> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(FtbAttendanceLineSchedulingConfig::getGroupId, groupId);
        FtbAttendanceLineSchedulingConfig ftbAttendanceLineSchedulingConfig = ftbAttendanceLineSchedulingConfigMapper.selectOne(queryWrapper);
        if (Objects.isNull(ftbAttendanceLineSchedulingConfig)) {
            return getFtbAttendanceLineSchedulingConfig(groupId);
        }
        List<String> memberList = StringUtil.isEmpty(ftbAttendanceLineSchedulingConfig.getMembers()) ? Lists.newArrayList() : Arrays.asList(ftbAttendanceLineSchedulingConfig.getMembers().split(","));
        List<String> notifyPersonList = StringUtil.isEmpty(ftbAttendanceLineSchedulingConfig.getNotifyPerson()) ? Lists.newArrayList() : Arrays.asList(ftbAttendanceLineSchedulingConfig.getNotifyPerson().split(","));
        ActionResult<List<UserBoundVO>> userList = v2UserApi.getUserPrimaryBoundBatch(CollUtil.unionAll(memberList, notifyPersonList), null);
        if (200 == userList.getCode() && CollectionUtil.isNotEmpty(userList.getData())) {
            List<AttendanceGroupUserVo> collect = userList.getData().stream().map(userPrimaryPositionDetailVO -> {
                AttendanceGroupUserVo groupUserVo = new AttendanceGroupUserVo();
                groupUserVo.setUserId(userPrimaryPositionDetailVO.getId());
                groupUserVo.setRealName(userPrimaryPositionDetailVO.getUserName());
                groupUserVo.setHeadIcon(UploaderUtil.uploaderImg(userPrimaryPositionDetailVO.getHeadIcon()));
                groupUserVo.setPositionName(userPrimaryPositionDetailVO.getPositionName());
                return groupUserVo;
            }).collect(Collectors.toList());
            ftbAttendanceLineSchedulingConfig.setMemberList(collect.stream().filter(vo -> memberList.contains(vo.getUserId())).collect(Collectors.toList()));
            ftbAttendanceLineSchedulingConfig.setNotifyPersonList(collect.stream().filter(vo -> notifyPersonList.contains(vo.getUserId())).collect(Collectors.toList()));
        }
        return ftbAttendanceLineSchedulingConfig;
    }

    private FtbAttendanceLineSchedulingConfig getFtbAttendanceLineSchedulingConfig(String groupId) {
        FtbAttendanceLineSchedulingConfig lineSchedulingConfig = new FtbAttendanceLineSchedulingConfig();
        lineSchedulingConfig.setGroupId(groupId);
        lineSchedulingConfig.setMembersType(1);
        lineSchedulingConfig.setStartCheckInLimit(BigDecimal.valueOf(2));
        lineSchedulingConfig.setEndCheckInLimit(BigDecimal.valueOf(5));
        lineSchedulingConfig.setStartType(1);
        lineSchedulingConfig.setStartTime("00:00");
        lineSchedulingConfig.setEndType(1);
        lineSchedulingConfig.setEndTime("23:00");
        lineSchedulingConfig.setNotifyTimeType(1);
        lineSchedulingConfig.setMemberList(List.of());
        lineSchedulingConfig.setNotifyPersonList(List.of());
        return lineSchedulingConfig;
    }

    @Override
    public void noticeLineScheduling(String tenantId) {
        if (StringUtil.isEmpty(tenantId)) return;
        String key = String.format(ATTENDANCE_LINE_SCHEDULING_NOTICE, tenantId);
        List<FtbAttendanceLineSchedulingConfig> list1;
        if(redisTemplate.hasKey(key)) {
            String value = redisTemplate.opsForValue().get(key);
            list1 = JSON.parseArray(value, FtbAttendanceLineSchedulingConfig.class);
        }else {
            //划线排班配置
            list1 = lambdaQuery().list();
            redisTemplate.opsForValue().set(key, JSON.toJSONString(list1));
        }
        Date date = new Date();
        String minute = DateUtil.format(date, "HH:mm");
        if (CollUtil.isEmpty(list1) || list1.stream().filter(config->Objects.nonNull(config.getNotifyTime())).noneMatch(config -> config.getNotifyTime().equals(minute))) {
            return;
        }

        List<AttendanceGroup> list = attendanceGroupService.list(new LambdaQueryWrapper<AttendanceGroup>().eq(AttendanceGroup::getLineSchedulingSetting, true).eq(AttendanceGroup::getDeleteMark, 0));
        if (CollUtil.isEmpty(list)) {
            log.error("未查询到启用划线排班的考勤组");
            return;
        }
        List<String> groupIds = list.stream().map(AttendanceGroup::getId).collect(Collectors.toList());
        Map<String, String> group2ManagerMap = list.stream().filter(vo -> StringUtil.isNotEmpty(vo.getManagerId())).collect(Collectors.toMap(AttendanceGroup::getId, AttendanceGroup::getManagerId));
        Map<String, String> group2NameMap = list.stream().collect(Collectors.toMap(AttendanceGroup::getId, AttendanceGroup::getGroupName));
        Map<String, String> org2GroupMap = list.stream().collect(Collectors.toMap(AttendanceGroup::getId, AttendanceGroup::getOrgId));
        //获取当前考勤组成员
        Date day = DateUtil.beginOfDay(date);
        List<AttendanceGroupUser> attendanceGroupUserVos = attendanceGroupUserService.getAttendanceGroupUsersOfSecondment(day, day, null, groupIds, Boolean.TRUE);
        if (CollUtil.isEmpty(attendanceGroupUserVos)) {
            return;
        }
        List<String> allUserIds = attendanceGroupUserVos.stream().map(AttendanceGroupUser::getUserId).distinct().collect(Collectors.toList());
        //获取组织下员工信息
        StaffRosterListReq staffRosterListReq = new StaffRosterListReq();
        staffRosterListReq.setPageSize(-1);
        staffRosterListReq.setIsQueryAuth("0");
        staffRosterListReq.setUserIds(allUserIds);
        ActionResult<PageListVO<FtbPersonnelsStaffRosterDto>> pageListVOActionResult = ftbPersonnelsRosterManagerApi.postWithSalary(staffRosterListReq);
        PageListVO<FtbPersonnelsStaffRosterDto> data = pageListVOActionResult.getData();
        List<FtbPersonnelsStaffRosterDto> userBaseDtoList = data.getList();
        Map<String, List<FtbPersonnelsStaffRosterDto>> userBaseMap = userBaseDtoList.stream().collect(Collectors.groupingBy(FtbPersonnelsStaffRosterDto::getCurrOrg));
        Map<String, FtbAttendanceLineSchedulingConfig> lineConfigMap = list1.stream().collect(Collectors.toMap(FtbAttendanceLineSchedulingConfig::getGroupId, Function.identity()));
        List<String> notifyPositionList = list1.stream().flatMap(config -> StringUtil.isEmpty(config.getNotifyPosition()) ? Stream.of() : Stream.of(StringUtil.split(config.getNotifyPosition(), ","))).collect(Collectors.toList());
        QueryPositionUserListDTO queryPositionUserListDTO = new QueryPositionUserListDTO();
        queryPositionUserListDTO.setTenantId(tenantId);
        queryPositionUserListDTO.setPositionIds(notifyPositionList);
        ActionResult<List<PositionListUserVO>> listActionResult = v2PositionApi.listPositionTreeUser(queryPositionUserListDTO);
        List<PositionListUserVO> data1 = listActionResult.getData();
        Map<String, List<String>> positionUsersMap = data1.stream().collect(Collectors.toMap(PositionListUserVO::getId, userVo->userVo.getUserList().stream().map(UserBoundVO::getId).distinct().collect(Collectors.toList())));
        //查询这些考勤组当天的排班信息
        List<FtbAttendanceDailyRule> allRules = attendanceDailyRuleService.list(new LambdaQueryWrapper<FtbAttendanceDailyRule>()
                .in(FtbAttendanceDailyRule::getGroupId, groupIds)
                .eq(FtbAttendanceDailyRule::getDay, day)
                .in(FtbAttendanceDailyRule::getUserId, allUserIds));
        Map<String, List<FtbAttendanceDailyRule>> ruleMap = allRules.stream().collect(Collectors.groupingBy(FtbAttendanceDailyRule::getGroupId));
        List<String> noticeUserIds = CollUtil.newArrayList();
        List<String> noticeAdminUserIds = CollUtil.newArrayList();
        Map<String, AttendanceGroupParam> groupParamMap = new HashMap<>();
        attendanceGroupUserVos.stream().collect(Collectors.groupingBy(AttendanceGroupUser::getGroupId)).forEach((groupId, userList) -> {
            String orgId = org2GroupMap.get(groupId);
            if (StringUtil.isEmpty(orgId)) {
                return;
            }
            List<FtbAttendanceDailyRule> rules = ruleMap.getOrDefault(groupId, Lists.newArrayList());
            List<String> userIdOfRule = rules.stream().map(FtbAttendanceDailyRule::getUserId).distinct().collect(Collectors.toList());
            List<String> userIds = userList.stream().map(AttendanceGroupUser::getUserId).distinct().collect(Collectors.toList());
            FtbAttendanceLineSchedulingConfig ftbAttendanceLineSchedulingConfig = lineConfigMap.get(groupId);
            if (Objects.isNull(ftbAttendanceLineSchedulingConfig)) {
                return;
            }
            if (!StringUtil.equals(ftbAttendanceLineSchedulingConfig.getNotifyTime(), minute)) {
                return;
            }
            List<FtbPersonnelsStaffRosterDto> userBaseList = userBaseMap.get(orgId);
            if (CollUtil.isEmpty(userBaseList)) {
                return;
            }
            lineSchedulesConfigFilter(userIds, ftbAttendanceLineSchedulingConfig, userBaseList);
            if (CollUtil.isEmpty(userIds)) {
                return;
            }
            userIds.removeAll(userIdOfRule);
            noticeUserIds.addAll(userIds);
            //负责人未排班通知
            List<String> notifyPersonArr = StringUtil.isEmpty(ftbAttendanceLineSchedulingConfig.getNotifyPerson()) ? CollUtil.newArrayList() : Arrays.stream(StringUtil.split(ftbAttendanceLineSchedulingConfig.getNotifyPerson(), ",")).collect(Collectors.toList());
            List<String> notifyPositionArr = StringUtil.isEmpty(ftbAttendanceLineSchedulingConfig.getNotifyPosition()) ? CollUtil.newArrayList() : Arrays.stream(StringUtil.split(ftbAttendanceLineSchedulingConfig.getNotifyPosition(), ",")).collect(Collectors.toList());
            List<String> notifyPositionUserIds = notifyPositionArr.stream().flatMap(positionId -> positionUsersMap.getOrDefault(positionId, CollUtil.newArrayList()).stream()).collect(Collectors.toList());
            notifyPersonArr.addAll(notifyPositionUserIds);
            if (CollUtil.isEmpty(notifyPersonArr) && group2ManagerMap.containsKey(groupId)) {
                notifyPersonArr.add(group2ManagerMap.get(groupId));
            }
            noticeAdminUserIds.addAll(notifyPersonArr);
            AttendanceGroupParam attendanceGroupParam = new AttendanceGroupParam(groupId, group2NameMap.get(groupId));
            notifyPersonArr.forEach(userId -> {
                if (!groupParamMap.containsKey(userId)) {
                    groupParamMap.put(userId, attendanceGroupParam);
                }
            });

        });
        //被排班人未排班通知
        LineShiftChangeNoticeModel shiftChangeNoticeModel = new LineShiftChangeNoticeModel();
        shiftChangeNoticeModel.setTenantId(StringUtil.isEmpty(tenantId) ? UserProvider.getUser().getTenantId() : tenantId);
        shiftChangeNoticeModel.setUserIds(noticeUserIds);
        shiftChangeNoticeModel.setDay(day);
        shiftChangeNoticeModel.setPeriods(List.of(LineDrawingPeriodDto.builder().day(day).build()));
        shiftChangeNoticeModel.setAttendanceNoticeEnum(AttendanceNoticeEnum.LINE_SHIFT_CHANG);
        attendanceNoticeHandler.send(shiftChangeNoticeModel);
        //查询权限
        //查询moduleId
        SystemEntity systemVO = systemApi.getSystemVO();
        String systemId = systemVO.getId();
        List<ModuleEntity> modules = moduleApi.getListWithoutSystemId(systemId, "APP", "考勤管理", tenantId);
        ModuleEntity moduleEntity = modules.stream().findFirst().orElse(null);
        if (Objects.isNull(moduleEntity)) {
            return;
        }
        Map<String, List<String>> userId2GroupIds = attendanceGroupUserVos.stream().filter(vo -> noticeAdminUserIds.contains(vo.getUserId())).collect(Collectors.groupingBy(AttendanceGroupUser::getUserId, Collectors.mapping(vo -> org2GroupMap.get(vo.getGroupId()), Collectors.toList())));
        Map<String, List<String>> stringListMap = ftbAuthorityApi.batchAuthOrganizesAllForUserIdsAndTenantId(noticeAdminUserIds, 0, moduleEntity.getId(), tenantId);
        //清除没有划线排班考勤组权限的负责人
        for (Map.Entry<String, List<String>> entry : userId2GroupIds.entrySet()) {
            List<String> orgIds = entry.getValue();
            List<String> strings = stringListMap.get(entry.getKey());
            if (CollUtil.isEmpty(strings)) {
                continue;
            }
            List<String> collect = strings.stream().filter(orgIds::contains).collect(Collectors.toList());
            if (CollUtil.isEmpty(collect)) {
                continue;
            }
            //负责人未排班通知
            shiftChangeNoticeModel.setTenantId(StringUtil.isEmpty(tenantId) ? UserProvider.getUser().getTenantId() : tenantId);
            shiftChangeNoticeModel.setUserIds(List.of(entry.getKey()));
            shiftChangeNoticeModel.setDay(day);
            shiftChangeNoticeModel.setPeriods(List.of(LineDrawingPeriodDto.builder().day(day).build()));
            shiftChangeNoticeModel.setModuleId(moduleEntity.getId());
            shiftChangeNoticeModel.setAttendanceGroup(groupParamMap.get(entry.getKey()));
            shiftChangeNoticeModel.setAttendanceNoticeEnum(AttendanceNoticeEnum.LINE_SHIFT_NOT_SCHEDUING);
            attendanceNoticeHandler.send(shiftChangeNoticeModel);
        }

    }

    @Override
    public boolean saveOrUpdateLineSchedulingConfig(FtbAttendanceLineSchedulingConfig config) {
        FtbAttendanceLineSchedulingConfig oldConfig = lambdaQuery().eq(FtbAttendanceLineSchedulingConfig::getGroupId, config.getGroupId()).last("limit 1").one();
        if(Objects.nonNull(oldConfig)){
            config.setId(oldConfig.getId());
        }
        if (saveOrUpdate(config)) {
            redisTemplate.delete(String.format(ATTENDANCE_LINE_SCHEDULING_NOTICE, UserProvider.getUser().getTenantId()));
            Date date = new Date();
            List<AttendanceGroupUser> attendanceGroupUsers = attendanceGroupUserService.queryByUsersAndGroupFilterSecondment(date, date, List.of(), CollUtil.newArrayList(config.getGroupId()));
            List<String> userIds = attendanceGroupUsers.stream().map(AttendanceGroupUser::getUserId).distinct().collect(Collectors.toList());
            List<String> oldUserIds = CollUtil.newArrayList();
            if(Objects.nonNull(oldConfig)) {
                oldUserIds.addAll(userIds);
                attendanceDailyRuleService.getFtbAttendanceLineSchedulingConfig(oldUserIds, oldConfig);
            }
            attendanceDailyRuleService.getFtbAttendanceLineSchedulingConfig(userIds, config);
            oldUserIds.removeAll(userIds);
            if(CollUtil.isEmpty(oldUserIds)) {
                return true;
            }
            attendanceDailyRuleService.clearGroupRule(config.getGroupId(), oldUserIds, date,null);
            return true;
        }
        return false;
    }

    private void lineSchedulesConfigFilter(List<String> userIds, FtbAttendanceLineSchedulingConfig lineSchedulingConfig, List<FtbPersonnelsStaffRosterDto> userList) {
        //划线排班配置过滤
        if (CollUtil.isNotEmpty(userIds)) {
            return;
        }
        if (Objects.isNull(lineSchedulingConfig)) {
            userIds.clear();
            return;
        }
        List<String> workNatureArr = StringUtil.isEmpty(lineSchedulingConfig.getWorkNature()) ? List.of() : Arrays.asList(StringUtil.split(lineSchedulingConfig.getWorkNature(), ","));
        List<String> employeeTypeArr = StringUtil.isEmpty(lineSchedulingConfig.getEmployeeType()) ? List.of() : Arrays.asList(StringUtil.split(lineSchedulingConfig.getEmployeeType(), ","));
        List<String> positionArr = StringUtil.isEmpty(lineSchedulingConfig.getPosition()) ? List.of() : Arrays.asList(StringUtil.split(lineSchedulingConfig.getPosition(), ","));
        List<String> memberArr = StringUtil.isEmpty(lineSchedulingConfig.getMembers()) ? List.of() : Arrays.asList(StringUtil.split(lineSchedulingConfig.getMembers(), ","));
        if (CollUtil.isEmpty(workNatureArr) && CollUtil.isEmpty(employeeTypeArr) && CollUtil.isEmpty(positionArr) && !Objects.equals(lineSchedulingConfig.getMembersType(), 0) && CollUtil.isEmpty(memberArr)) {
            return;
        }
        boolean hasOnboarding = workNatureArr.contains("301");
        List<String> userIdsForConfigFilter = userList.stream()
                .filter(dto -> (CollUtil.isNotEmpty(workNatureArr) && workNatureArr.contains(dto.getWorkerStatus()))
                        && ((hasOnboarding && StringUtil.isEmpty(dto.getWorkerType())) || employeeTypeArr.contains("-1") || CollUtil.isNotEmpty(employeeTypeArr) && employeeTypeArr.contains(dto.getWorkerType()))
                        || (CollUtil.isNotEmpty(positionArr) && positionArr.contains(dto.getCurrPosition()))
                        || Objects.equals(lineSchedulingConfig.getMembersType(), 0)
                        || (CollUtil.isNotEmpty(memberArr) && memberArr.contains(dto.getUserId()))).map(FtbPersonnelsStaffRosterDto::getUserId).collect(Collectors.toList());
        userIds.removeIf(userId -> !userIdsForConfigFilter.contains(userId));
    }
}