package cn.iocoder.yudao.module.smartfactory.framework.job.staffSalary;

import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.bpm.api.oa.BpmOALoanApi;
import cn.iocoder.yudao.module.bpm.api.oa.vo.loan.BpmOALoanSumDTO;
import cn.iocoder.yudao.module.smartfactory.dal.dataobject.attendance.StaffAttendanceRecordDO;
import cn.iocoder.yudao.module.smartfactory.dal.dataobject.staff.StaffDO;
import cn.iocoder.yudao.module.smartfactory.dal.dataobject.staffsalary.StaffSalaryDO;
import cn.iocoder.yudao.module.smartfactory.dal.mysql.staffsalary.StaffSalaryMapper;
import cn.iocoder.yudao.module.smartfactory.service.attendance.StaffAttendanceRecordService;
import cn.iocoder.yudao.module.smartfactory.service.staff.StaffService;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;

/**
 * 工厂员工 月工资计算 定时任务
 */
@Component
@Slf4j
public class StaffSalaryJob {

    @Resource
    private StaffAttendanceRecordService staffAttendanceRecordService;

    @Resource
    private StaffService staffService;

    @Resource
    private StaffSalaryMapper staffSalaryMapper;

    @Resource
    private BpmOALoanApi loanApi;

    @XxlJob("staffSalaryJob")
    @TenantJob // --- ⚠️ 这个注解 会将租户列表拉出来 完了后逐个租户执行 定时任务需要注意
    public ReturnT<String> execute() throws Exception {

        // 获取当前日期 上个月得第一天
        LocalDate firstDayOfMonth = LocalDate.now().minusMonths(1).withDayOfMonth(1);
        // 获取当前日期 上个月得月份
        String month = LocalDate.now().minusMonths(1).format(DateTimeFormatter.ofPattern("yyyy-MM"));
        // 获取两种考勤类型得 日期区间
        // 类型一 上月26日 - 本月25日
        LocalDate[] firstType = new LocalDate[]{
                firstDayOfMonth.minusMonths(1).withDayOfMonth(26),
                firstDayOfMonth.withDayOfMonth(25)
        };
        // 类型二 本月初 - 本月底
        LocalDate[] secondType = new LocalDate[]{
                firstDayOfMonth,
                firstDayOfMonth.withDayOfMonth(firstDayOfMonth.lengthOfMonth())
        };

        // 获取考勤记录
        List<StaffAttendanceRecordDO> recordDOS = staffAttendanceRecordService.getRecordListGroupByStaffId(month);

        // 获取厂区对应的考勤类型
        Map<Long, Integer> typeMap = recordDOS.stream()
                .collect(Collectors.groupingBy(StaffAttendanceRecordDO::getFactoryId))
                .entrySet().stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        entry -> entry.getValue().get(0).getType()
                ));

        // 获取厂区得员工信息
        List<StaffDO> staffDOS = staffService.getListByFactoryIds(typeMap.keySet());
        Map<Long, StaffDO> staffMap = convertMap(staffDOS, StaffDO::getId);

        // 获取 用户map key为工厂编号 value 为 用户id集合
        Map<Long, List<Long>> facrtoryMap = staffDOS.stream()
                .collect(Collectors.groupingBy(
                        StaffDO::getFactoryId, // key
                        Collectors.mapping(StaffDO::getId, Collectors.toList()) // value
                ));

        // 重新组合 考勤类型对应用户得map集合
        Map<Integer, List<Long>> resultMap = typeMap.entrySet().stream()
                .filter(entry -> facrtoryMap.containsKey(entry.getKey())) // 只保留 key 相同的项
                .collect(Collectors.toMap(
                        Map.Entry::getValue, // typeMap 中的 value 作为新 map 的 key
                        entry -> facrtoryMap.get(entry.getKey()) // staffDOS 中的 value 作为新 map 的 value
                ));

        // 遍历 resultMap
        Map<Long, List<StaffAttendanceRecordDO>> recordMap = new HashMap<>();
        for (Map.Entry<Integer, List<Long>> entry : resultMap.entrySet()) {

            Integer type = entry.getKey();
            List<Long> staffIds = entry.getValue();

            LocalDate[] date;
            if (type == 1) {
                date = firstType;
            } else if (type == 2) {
                date = secondType;
            } else {
                date = new LocalDate[2];
            }

            Predicate<StaffAttendanceRecordDO> filter = item ->
                    staffIds.contains(item.getStaffId())
                            && !item.getPunchTime().isAfter(date[1])
                            && !item.getPunchTime().isBefore(date[0]);

            // 过滤数据
            Map<Long, List<StaffAttendanceRecordDO>> typeOne = recordDOS.stream()
                    .filter(filter)
                    .collect(Collectors.groupingBy(StaffAttendanceRecordDO::getStaffId));

            recordMap.putAll(typeOne);
        }

        // 获取员工借支记录
        List<BpmOALoanSumDTO> loanDOS = loanApi.getListByStaffId(recordMap.keySet(), month).getCheckedData();
        Map<Long, BigDecimal> loanMap = loanDOS.stream().collect(Collectors.toMap(BpmOALoanSumDTO::getSfUserId, BpmOALoanSumDTO::getTotalAmount));

        // 计算工资
        List<StaffSalaryDO> salaryDOS = new ArrayList<>();
        for (Map.Entry<Long, List<StaffAttendanceRecordDO>> entry : recordMap.entrySet()) {
            Long staffId = entry.getKey();
            List<StaffAttendanceRecordDO> items = entry.getValue();

            // 创建薪资DO
            StaffSalaryDO salaryDO = new StaffSalaryDO()
                    .setStaffId(staffId)
                    .setMonth(month)
                    .setAttendanceDays(items.size())
                    .setFactoryId(staffMap.get(staffId).getFactoryId())
                    .setReturnAmount(loanMap.getOrDefault(staffId, BigDecimal.ZERO));

            // 计算当月的工作天数
            int type = typeMap.get(staffMap.get(staffId).getFactoryId());
            int workDays = 0;
            if (type == 1) {
                long days = ChronoUnit.DAYS.between(firstType[0],
                        firstDayOfMonth.minusMonths(1).withDayOfMonth(firstDayOfMonth.minusMonths(1).lengthOfMonth()));

                workDays = (int) days + 25;
            } else if (type == 2) {
                long days = ChronoUnit.DAYS.between(secondType[0], secondType[1]);

                workDays = (int) days;
            }

            // 计算应发工资
            if (staffMap.get(staffId).getSalary() != null) {  // 没有录入基本薪资时，不做计算
                BigDecimal payableAmount = staffMap.get(staffId).getSalary().divide(BigDecimal.valueOf(workDays), RoundingMode.HALF_UP)
                        .multiply(BigDecimal.valueOf(salaryDO.getAttendanceDays()));
                // 设置应发工资
                salaryDO.setPayableAmount(payableAmount);
                // 设置实发工资
                salaryDO.setRealAmount(payableAmount);
            }

            // 插入集合
            salaryDOS.add(salaryDO);
        }

        // 插入工资记录
        staffSalaryMapper.insertBatch(salaryDOS);

        // 返回执行成功
        return ReturnT.SUCCESS;
    }
}
