package jnpf.attendance.service.handle.notification;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import jnpf.attendance.service.AttendanceClockInService;
import jnpf.attendance.service.AttendanceGroupService;
import jnpf.attendance.service.DailyRuleChangeService;
import jnpf.attendance.service.handle.notice.AttendanceNoticeHandler;
import jnpf.base.UserInfo;
import jnpf.constants.MessageTopicConstants;
import jnpf.entity.attendance.DailyRuleChange;
import jnpf.entity.attendance.FtbAttendanceDailyRule;
import jnpf.enums.attendance.AttendanceNoticeEnum;
import jnpf.enums.attendance.AttendanceTypeEnum;
import jnpf.enums.attendance.TriggerSceneEnum;
import jnpf.model.attendance.dto.LineDrawingPeriodDto;
import jnpf.model.attendance.dto.LineDrawingSchedulesConfigDto;
import jnpf.model.attendance.event.StatisticsBatchClearDto;
import jnpf.model.attendance.event.StatisticsSingleDto;
import jnpf.model.attendance.model.LineShiftChangeNoticeModel;
import jnpf.model.attendance.model.ShiftChangeNoticeModel;
import jnpf.model.attendance.model.SystemTypeChangeNoticeModel;
import jnpf.model.attendance.vo.AttendanceShiftSettingVo;
import jnpf.model.attendance.vo.DailyRuleResultVo;
import jnpf.model.attendance.vo.UserDayVo;
import jnpf.util.DateDetail;
import jnpf.util.StringUtil;
import jnpf.util.UserProvider;
import jnpf.util.attendance.DailyRuleUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

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

@Component
@Slf4j
public class AttendanceRuleNotificationHandle {
    @Resource
    private RocketMQTemplate rocketMqTemplate;
    @Autowired
    private AttendanceClockInService attendanceClockInService;
    @Autowired
    private AttendanceNoticeHandler attendanceNoticeHandler;
    @Autowired
    private DailyRuleChangeService dailyRuleChangeService;

    public void notificationHandle(List<DailyRuleResultVo> resultList, String tenantId, Boolean isSync, Boolean isNotificationClock) {
        UserInfo user = UserProvider.getUser();
        if (StringUtil.isNotNull(tenantId)) {
            user.setTenantId(tenantId);
        }
        if (isSync) {
            notificationClockHandleSync(resultList, user, isNotificationClock);
            return;
        }
        notificationMqHandle(resultList, user, isNotificationClock);
    }

    public void notificationHandle(List<DailyRuleResultVo> resultList, Boolean isSync, Boolean isNotificationClock) {
        notificationHandle(resultList, null, isSync, isNotificationClock);
    }

    public void notificationHandle(List<DailyRuleResultVo> resultList, Boolean isSync) {
        notificationHandle(resultList, isSync, Boolean.TRUE);
    }

    public void notificationHandle(List<DailyRuleResultVo> resultList, String tenantId) {
        notificationHandle(resultList, tenantId, Boolean.FALSE, Boolean.TRUE);
    }


    public void notificationClearHandle(String tenantId, String fromGroupId, List<String> userIds, Date day) {
        if (CollUtil.isEmpty(userIds)) {
            return;
        }
        //调用批量清除日统计数据接口s
        StatisticsBatchClearDto batchClearDto = StatisticsBatchClearDto.builder()
                .tenantId(StringUtil.isEmpty(tenantId) ? UserProvider.getUser().getTenantId() : tenantId)
                .groupId(fromGroupId)
                .userIdList(userIds)
                .startDay(day)
                .build();
        Message<StatisticsBatchClearDto> clearMessage = MessageBuilder.withPayload(batchClearDto).build();
        rocketMqTemplate.syncSend(MessageTopicConstants.ATTENDANCE_STATISTICS_BATCH_CLEAR_TOPIC, clearMessage, 3000L, 2);
    }


    /**
     * 规则变动发送通知
     *
     * @param resultList 规则变动结果
     * @param tenantId   租户id
     */
    @Async("threadPoolTaskExecutor")
    public void sendSystemNotice(List<DailyRuleResultVo> resultList, String tenantId) {
        resultList.stream()
                .filter(dailyRuleResultVo -> Objects.nonNull(dailyRuleResultVo.getFromType())
                        && (Objects.equals(dailyRuleResultVo.getFromType(), AttendanceTypeEnum.ORDINARY.getCode())
                        || Objects.equals(dailyRuleResultVo.getFromType(), AttendanceTypeEnum.REST.getCode())
                        || Objects.equals(dailyRuleResultVo.getFromType(), AttendanceTypeEnum.HOLIDAYS.getCode())
                        || Objects.equals(dailyRuleResultVo.getFromType(), AttendanceTypeEnum.DEDUCTION_HOLIDAYS.getCode())
                        || Objects.equals(dailyRuleResultVo.getFromType(), AttendanceTypeEnum.EXCHANGE_HOLIDAYS.getCode()))
                        && (Objects.equals(dailyRuleResultVo.getToType(), AttendanceTypeEnum.ORDINARY.getCode())
                        || Objects.equals(dailyRuleResultVo.getToType(), AttendanceTypeEnum.REST.getCode())
                        || Objects.equals(dailyRuleResultVo.getToType(), AttendanceTypeEnum.HOLIDAYS.getCode())
                        || Objects.equals(dailyRuleResultVo.getToType(), AttendanceTypeEnum.DEDUCTION_HOLIDAYS.getCode())
                        || Objects.equals(dailyRuleResultVo.getToType(), AttendanceTypeEnum.EXCHANGE_HOLIDAYS.getCode())
                        || Objects.isNull(dailyRuleResultVo.getToType()))).collect(Collectors.groupingBy(DailyRuleResultVo::getUserId))
                .forEach((userId, results) -> {
                    ShiftChangeNoticeModel shiftChangeNoticeModel = new ShiftChangeNoticeModel();
                    shiftChangeNoticeModel.setTenantId(StringUtil.isEmpty(tenantId) ? UserProvider.getUser().getTenantId() : tenantId);
                    shiftChangeNoticeModel.setUserId(userId);
                    shiftChangeNoticeModel.setDailyRuleResultVos(results);
                    shiftChangeNoticeModel.setAttendanceNoticeEnum(AttendanceNoticeEnum.SHIFT_CHANG);
                    attendanceNoticeHandler.send(shiftChangeNoticeModel);
                });
    }

    @Async("threadPoolTaskExecutor")
    public void sendNoticeBatch(List<String> selfAndChildrenGroupIds, Integer systemType) {
        selfAndChildrenGroupIds.forEach(itemGroupId -> sendNotice(itemGroupId, systemType));
    }

    private void sendNotice(String groupId, Integer systemType) {
        SystemTypeChangeNoticeModel systemTypeChangeNoticeModel = new SystemTypeChangeNoticeModel();
        systemTypeChangeNoticeModel.setSystemType(systemType);
        systemTypeChangeNoticeModel.setGroupId(groupId);
        systemTypeChangeNoticeModel.setIsAdmin(Boolean.FALSE);
        systemTypeChangeNoticeModel.setTenantId(UserProvider.getUser().getTenantId());
        systemTypeChangeNoticeModel.setAttendanceNoticeEnum(AttendanceNoticeEnum.RULE_CHANGE_SHIFT_SYSTEM);
        attendanceNoticeHandler.send(systemTypeChangeNoticeModel);
        systemTypeChangeNoticeModel.setIsAdmin(Boolean.TRUE);
        attendanceNoticeHandler.send(systemTypeChangeNoticeModel);
    }

    public void sendSystemNoticeForLine(List<DailyRuleResultVo> resultList, LineDrawingSchedulesConfigDto configDto, String tenantId) {
        sendSystemNoticeForLine(resultList, configDto, null, tenantId);
    }

    /**
     * 划线排班规则变动发送通知
     *
     * @param configDto 规则
     * @param tenantId   租户id
     */
    public void sendSystemNoticeForLine(List<DailyRuleResultVo> resultList, LineDrawingSchedulesConfigDto configDto, List<Date> days, String tenantId) {
        LineShiftChangeNoticeModel shiftChangeNoticeModel = new LineShiftChangeNoticeModel();
        shiftChangeNoticeModel.setAttendanceNoticeEnum(AttendanceNoticeEnum.LINE_SHIFT_CHANG);
        shiftChangeNoticeModel.setTenantId(StringUtil.isEmpty(tenantId) ? UserProvider.getUser().getTenantId() : tenantId);
        List<Date> daysList = CollUtil.isNotEmpty(days) ? days : Arrays.stream(StringUtil.split(configDto.getDays(), ",")).map(jnpf.util.DateUtil::stringToDates).collect(Collectors.toList());
        Map<String, List<DailyRuleResultVo>> collect = resultList.stream().collect(Collectors.groupingBy(vo -> vo.getUserId() + DateDetail.getDate2Str(vo.getDate(), DateDetail.DF)));
        configDto.getUserList()
                .forEach(user -> {
                    List<LineDrawingPeriodDto> periods = user.getPeriods();
                    if (CollUtil.isEmpty(periods) && CollUtil.isEmpty(resultList)) {
                        return;
                    }
                    if (CollUtil.isNotEmpty(periods) && periods.stream().allMatch(vo -> StringUtil.isNotEmpty(vo.getId()))) {
                        return;
                    }
                    List<LineDrawingPeriodDto> periodList = CollUtil.newArrayList();
                    List<Date> collect1 = periods.stream().map(LineDrawingPeriodDto::getDay).distinct().sorted().collect(Collectors.toList());
                    if (CollUtil.isEmpty(collect1)) {
                        collect1 = daysList;
                    }
                    collect1.forEach(day -> {
                        String key = user.getUserId() + DateDetail.getDate2Str(day, DateDetail.DF);
                        if (!collect.containsKey(key)) {
                            return;
                        }
                        if (CollUtil.isEmpty(periods)) {
                            periodList.add(LineDrawingPeriodDto.builder()
                                    .day(day)
                                    .start("")
                                    .build());
                            return;
                        }
                        periodList.addAll(BeanUtil.copyToList(periods, LineDrawingPeriodDto.class).stream().peek(vo -> vo.setDay(day)).collect(Collectors.toList()));
                    });
                    if (CollUtil.isEmpty(periodList)) {
                        return;
                    }
                    shiftChangeNoticeModel.setUserIds(List.of(user.getUserId()));
                    shiftChangeNoticeModel.setPeriods(periodList);
                    attendanceNoticeHandler.send(shiftChangeNoticeModel);
                });
    }

    /**
     * 划线排班规则变动发送通知
     *
     * @param rules 规则
     * @param tenantId   租户id
     */
    @Async("threadPoolTaskExecutor")
    public void sendSystemNoticeForLine2(List<DailyRuleResultVo> resultList, List<FtbAttendanceDailyRule> rules, String tenantId) {
        Map<String, List<DailyRuleResultVo>> collect = resultList.stream().collect(Collectors.groupingBy(vo -> vo.getUserId() + DateDetail.getDate2Str(vo.getDate(), DateDetail.DF)));
        LineShiftChangeNoticeModel shiftChangeNoticeModel = new LineShiftChangeNoticeModel();
        shiftChangeNoticeModel.setTenantId(StringUtil.isEmpty(tenantId) ? UserProvider.getUser().getTenantId() : tenantId);
        shiftChangeNoticeModel.setAttendanceNoticeEnum(AttendanceNoticeEnum.LINE_SHIFT_CHANG);
        rules.stream().filter(vo -> Objects.equals(vo.getAttendanceType(), AttendanceTypeEnum.LEAVE.getCode()) || Objects.equals(vo.getAttendanceType(), AttendanceTypeEnum.ORDINARY.getCode())).collect(Collectors.groupingBy(rule -> rule.getUserId() + DateDetail.getDate2Str(rule.getDay(), DateDetail.DF)))
                .forEach((key, userRules) -> {
                    FtbAttendanceDailyRule rule = userRules.stream().findFirst().orElse(null);
                    if (Objects.isNull(rule)) {
                        return;
                    }
                    if (!collect.containsKey(key)) {
                        return;
                    }
                    DateTime tomorrow = DateUtil.offsetDay(rule.getDay(), 1);
                    List<DailyRuleResultVo> orDefault = collect.getOrDefault(key, List.of());
                    boolean isClear = orDefault.stream().allMatch(vo -> Objects.isNull(vo.getType()));
                    List<LineDrawingPeriodDto> periods = DailyRuleUtil.mergeRules(userRules).stream().map(dayRule -> LineDrawingPeriodDto.builder()
                            .day(dayRule.getDay())
                            .startType(dayRule.getInPoint().after(tomorrow) ? 2 : 1)
                            .start(isClear ? "" : jnpf.util.DateUtil.dateToString(dayRule.getInPoint(), "HH:mm"))
                            .startType(dayRule.getOutPoint().after(tomorrow) ? 2 : 1)
                            .end(isClear ? "" : jnpf.util.DateUtil.dateToString(dayRule.getOutPoint(), "HH:mm"))
                            .build()).collect(Collectors.toList());
                    shiftChangeNoticeModel.setUserIds(List.of(rule.getUserId()));
                    shiftChangeNoticeModel.setPeriods(periods);
                    shiftChangeNoticeModel.setDay(rule.getDay());
                    attendanceNoticeHandler.send(shiftChangeNoticeModel);
                });
    }


    private void notificationClockHandleSync(List<DailyRuleResultVo> resultList, UserInfo user, boolean isNotificationClock) {
        if (isNotificationClock) {
            try {
                // 出勤变更通知
                List<UserDayVo> userDayList = resultList.stream()
                        .map(vo -> new UserDayVo(user.getTenantId(), vo.getGroupId(), vo.getUserId(), vo.getDate(), DateDetail.getDate2Str(vo.getDate(), DateDetail.DF)))
                        .collect(Collectors.collectingAndThen(
                                // 去除重复记录, 冲突时保留第一条
                                Collectors.toMap(u -> u.getUserId() + "_" + u.getDayStr(), Function.identity(), (existing, replacement) -> existing),
                                map -> new ArrayList<>(map.values())
                        ));
                dailyRuleChangeService.saveRecordBatch(userDayList.stream().map(userDayVo -> new DailyRuleChange(userDayVo.getGroupId(), userDayVo.getUserId(), userDayVo.getDay(), userDayVo.getTenantId())).collect(Collectors.toList()));
                attendanceClockInService.changeAttendanceRuleBatch(userDayList, user);
            } catch (Exception e) {
                log.error("排班通知打卡失败", e);
            }
        }
        //调用批量生成日统计数据接口
        Map<String, List<DailyRuleResultVo>> groupMap = resultList.stream().collect(Collectors.groupingBy(DailyRuleResultVo::getGroupId));
        groupMap.forEach((groupId, dayList) -> {
            Map<Date, List<DailyRuleResultVo>> dayMap = dayList.stream().collect(Collectors.groupingBy(DailyRuleResultVo::getDate));
            dayMap.forEach((date, userList) -> {
                notificationClearHandle(user.getTenantId(), groupId, userList.stream().filter(vo -> Objects.isNull(vo.getToType())).map(DailyRuleResultVo::getUserId).distinct().collect(Collectors.toList()), date);
                notificationStatistics(user.getTenantId(), groupId, date, userList.stream().map(DailyRuleResultVo::getUserId).distinct().collect(Collectors.toList()));
            });
        });

    }

    private void notificationMqHandle(List<DailyRuleResultVo> resultList, UserInfo user, boolean isNotificationClock) {
        if (isNotificationClock) {
            notificationClockMq(resultList, user);
        }
        //调用批量生成日统计数据接口
        Map<String, List<DailyRuleResultVo>> groupMap = resultList.stream().collect(Collectors.groupingBy(DailyRuleResultVo::getGroupId));
        groupMap.forEach((groupId, dayList) -> {
            Map<Date, List<DailyRuleResultVo>> dayMap = dayList.stream().collect(Collectors.groupingBy(DailyRuleResultVo::getDate));
            dayMap.forEach((date, userList) -> {
                notificationClearHandle(user.getTenantId(), groupId, userList.stream().filter(vo -> Objects.isNull(vo.getToType())).map(DailyRuleResultVo::getUserId).distinct().collect(Collectors.toList()), date);
                notificationStatistics(user.getTenantId(), groupId, date, userList.stream().map(DailyRuleResultVo::getUserId).distinct().collect(Collectors.toList()));
            });
        });
    }

    public void notificationClockMq(List<DailyRuleResultVo> resultList, UserInfo user) {
        //调用生成日统计数据接口
        // 出勤变更通知
        List<UserDayVo> userDayList = resultList.stream()
                .map(vo -> new UserDayVo(user.getTenantId(), vo.getGroupId(), vo.getUserId(), vo.getDate(), DateDetail.getDate2Str(vo.getDate(), DateDetail.DF)))
                .collect(Collectors.collectingAndThen(
                        // 去除重复记录, 冲突时保留第一条
                        Collectors.toMap(u -> u.getUserId() + "_" + u.getDayStr(), Function.identity(), (existing, replacement) -> existing),
                        map -> new ArrayList<>(map.values())
                ));
        Message<List<UserDayVo>> message = MessageBuilder.withPayload(userDayList).build();
        try {
            dailyRuleChangeService.saveRecordBatch(userDayList.stream().map(userDayVo -> new DailyRuleChange(userDayVo.getGroupId(), userDayVo.getUserId(), userDayVo.getDay(), userDayVo.getTenantId())).collect(Collectors.toList()));
            SendResult sendResult = rocketMqTemplate.syncSend(MessageTopicConstants.ATTENDANCE_NOTIFICATION_CLOCK_TOPIC, message, 3000L, 3);
            Assert.isFalse(!sendResult.getSendStatus().equals(SendStatus.SEND_OK), "排班通知打卡异常重试");
        } catch (Exception e) {
            log.error("AttendanceRuleNotificationHandle#notificationClockMq 排班通知打卡异常：{}", JSON.toJSONString(message), e);
            throw new RuntimeException("排班通知打卡异常", e);
        }
    }


    public void notificationStatistics(String tenantId, String groupId, Date date, List<String> userList) {
        //调用生成日统计数据接口
        StatisticsSingleDto courseEventDTO = StatisticsSingleDto.builder()
                .tenantId(tenantId)
                .groupId(groupId)
                .day(date)
                .triggerSceneEnum(TriggerSceneEnum.SCHEDULES)
                .build();
        userList.forEach(item -> {
            courseEventDTO.setUserId(item);
            Message<StatisticsSingleDto> message = MessageBuilder.withPayload(courseEventDTO).build();
            if (StrUtil.isBlank(tenantId) || StrUtil.isBlank(groupId)) {
                log.error("AttendanceRuleNotificationHandle#notificationStatistics 触发日统计租户ID或者考勤组ID为空：{}", JSON.toJSONString(courseEventDTO));
                return;
            }
            rocketMqTemplate.syncSend(MessageTopicConstants.ATTENDANCE_STATISTICS_SINGLE_TOPIC, message, 3000L, 2);
        });
    }

}