package jnpf.attendance.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jnpf.attendance.dto.AttendanceUserGroupVo;
import jnpf.attendance.mapper.AttendanceSealSettingMapper;
import jnpf.attendance.service.*;
import jnpf.base.service.SuperServiceImpl;
import jnpf.constants.RedisConstant;
import jnpf.entity.attendance.AttendanceBaseSetting;
import jnpf.entity.attendance.AttendanceDayStatistics;
import jnpf.entity.attendance.AttendanceSealSetting;
import jnpf.model.attendance.dto.MonthAutoSealSettingDto;
import jnpf.model.attendance.vo.attendance.MonthAutoSealSettingVo;
import jnpf.util.UserProvider;
import jnpf.util.attendance.DayStatisticsUtils;
import jnpf.util.context.ThreadContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

@Slf4j
@Service
public class AttendanceSealSettingServiceImpl extends SuperServiceImpl<AttendanceSealSettingMapper, AttendanceSealSetting> implements AttendanceSealSettingService {
    @Autowired
    private StringRedisTemplate redisUtil;
    @Resource
    private AttendanceGroupService groupService;
    @Resource(name = "ioIntensiveThreadPool")
    private ThreadPoolExecutor threadPoolExecutor;
    @Resource
    private PublicHolidayRulesService holidayRulesService;
    @Resource
    private AttendanceBaseSettingService baseSettingService;
    @Resource
    private AttendanceDayStatisticsService dayStatisticsService;

    @Override
    public MonthAutoSealSettingVo getAutoSealSettingInfo() {
        LambdaQueryWrapper<AttendanceSealSetting> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(AttendanceSealSetting::getDeleteMark, 0);
        queryWrapper.last("limit 1");
        AttendanceSealSetting sealSetting = this.getOne(queryWrapper);
        return Objects.isNull(sealSetting) ? null : MonthAutoSealSettingVo.builder()
                .id(sealSetting.getId())
                .autoSeal(sealSetting.getAutoSeal())
                .sealDay(sealSetting.getSealDay())
                .sealTime(sealSetting.getSealTime())
                .build();
    }

    @Override
    public Boolean autoSealSetting(MonthAutoSealSettingDto dto) {
        AttendanceSealSetting sealSetting = this.getById(dto.getId());
        Assert.notNull(sealSetting, "数据不存在");
        sealSetting.setAutoSeal(dto.getAutoSeal());
        sealSetting.setSealDay(dto.getSealDay());
        sealSetting.setSealTime(dto.getSealTime());
        this.updateById(sealSetting);
        //检测是否已存在晚走晚到
        String sealSettingKey = String.format(RedisConstant.ATTENDANCE_SEAL_SETTING, UserProvider.getUser().getTenantId());
        redisUtil.opsForValue().set(sealSettingKey, JSON.toJSONString(dto));
        return Boolean.TRUE;
    }

    @Override
    @Transactional
    public Boolean autoSealTimer(String tenantId) {
        // 从缓存获取自动封账设置
        String sealSettingKey = String.format(RedisConstant.ATTENDANCE_SEAL_SETTING, tenantId);
        MonthAutoSealSettingVo sealSettingVo;
        if (!Boolean.TRUE.equals(redisUtil.hasKey(sealSettingKey))) {
            sealSettingVo = this.getAutoSealSettingInfo();
            redisUtil.opsForValue().set(sealSettingKey, JSON.toJSONString(sealSettingVo));
            return Boolean.TRUE;
        }else{
            sealSettingVo = JSON.parseObject(redisUtil.opsForValue().get(sealSettingKey), MonthAutoSealSettingVo.class);
        }
        // 校验是否开启自动封账
        if (Objects.isNull(sealSettingVo) || sealSettingVo.getAutoSeal().equals(0)) {
            return Boolean.TRUE;
        }
        Integer sealDay = sealSettingVo.getSealDay();
        Date dateNew = new Date();
        // 判断当前时间点是否到了封账时间点
        if (!DayStatisticsUtils.isCurrentTimeEqualsSealTime(sealSettingVo.getSealTime())) {
            return Boolean.TRUE;
        }
        Date date = DateUtil.beginOfDay(dateNew);
        int day = DateUtil.dayOfMonth(date);
        if (!Objects.equals(sealDay, day)) {
            return Boolean.TRUE;
        }
        date = DateUtil.offsetMonth(date, -1);
        int month = DateUtil.month(date) + 1;
        int year = DateUtil.year(date);
        //生成封账数据
        createSeal(year, month);
        return Boolean.TRUE;
    }

    /**
     * 生成封账数据
     *
     * @param year     年份
     * @param month    月份
     */
    private void createSeal(int year, int month) {
        // 创建 YearMonth 对象
        YearMonth yearMonth = YearMonth.of(year, month);
        // 获取该月的第一天
        LocalDate startDate = yearMonth.atDay(1);
        // 获取该月的最后一天
        LocalDate endDate = yearMonth.atEndOfMonth();
        //数据量非常大,分批执行数据修改
        LambdaQueryWrapper<AttendanceDayStatistics> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.select(AttendanceDayStatistics::getId, AttendanceDayStatistics::getUserId);
        queryWrapper.between(AttendanceDayStatistics::getDate, startDate, endDate);
        List<AttendanceDayStatistics> dayStatisticsList = dayStatisticsService.list(queryWrapper);
        // 如果idList为空，则直接返回
        if (CollUtil.isEmpty(dayStatisticsList)) {
            return;
        }
        List<String> idList = dayStatisticsList.stream().map(AttendanceDayStatistics::getId).collect(Collectors.toList());
        List<String> userIdList = dayStatisticsList.stream().map(AttendanceDayStatistics::getUserId).distinct().collect(Collectors.toList());
        //数据量过大分批执行修改操作
        List<List<String>> batches = DayStatisticsUtils.partition(idList, 1000);
        for (List<String> batch : batches) {
            LambdaUpdateWrapper<AttendanceDayStatistics> updateWrapper = Wrappers.lambdaUpdate();
            updateWrapper.in(AttendanceDayStatistics::getId, batch);
            updateWrapper.set(AttendanceDayStatistics::getSeal, 1);
            dayStatisticsService.update(updateWrapper);
        }
        // 获取用户出勤换算比
        CompletableFuture.runAsync(ThreadContext.wrap(() -> {
            try{
                List<AttendanceUserGroupVo> attendanceUserGroupList = groupService.getAttendanceUserGroup(userIdList);
                List<String> groupIds = CollUtil.isNotEmpty(attendanceUserGroupList) ?
                        attendanceUserGroupList.stream().map(AttendanceUserGroupVo::getGroupId).distinct()
                                .collect(Collectors.toList()) : CollUtil.newArrayList();
                Map<String, AttendanceBaseSetting> groupBaseSettingMap = baseSettingService.getEnableBaseSettingAll(groupIds);
                Map<String, BigDecimal> ratioMap = new HashMap<>();
                if (CollUtil.isNotEmpty(attendanceUserGroupList)) {
                    attendanceUserGroupList.forEach(item -> {
                        if (CollUtil.isEmpty(groupBaseSettingMap) || !groupBaseSettingMap.containsKey(item.getGroupId()) ||
                                Objects.isNull(groupBaseSettingMap.get(item.getGroupId())) || Objects.isNull(groupBaseSettingMap.get(item.getGroupId()).getAttendanceRatio())) {
                            return;
                        }
                        ratioMap.put(item.getUserId(), groupBaseSettingMap.get(item.getGroupId()).getAttendanceRatio());
                    });
                }
                holidayRulesService.processPublicHoliday(yearMonth.toString(), userIdList, ratioMap);
            }catch (Exception e) {
                log.error("获取用户出勤换算比失败", e);
            }
        }), threadPoolExecutor);
    }
}
