package cn.iocoder.yudao.module.smartfactory.service.staffsalary;

import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
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.controller.admin.staffsalary.vo.*;
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.staff.StaffMapper;
import cn.iocoder.yudao.module.smartfactory.dal.mysql.staffsalary.StaffSalaryMapper;
import cn.iocoder.yudao.module.smartfactory.service.staff.StaffService;
import cn.iocoder.yudao.module.system.api.loan.LoanApi;
import cn.iocoder.yudao.module.system.api.loan.dto.LoanDTO;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

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.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.smartfactory.enums.ErrorCodeConstants.STAFF_SALARY_EXISTS;
import static cn.iocoder.yudao.module.smartfactory.enums.ErrorCodeConstants.STAFF_SALARY_NOT_EXISTS;

/**
 * 厂区员工工资 Service 实现类
 *
 * @author 符溶馨
 */
@Service
@Validated
public class StaffSalaryServiceImpl implements StaffSalaryService {

    @Resource
    private StaffSalaryMapper staffSalaryMapper;

    @Resource
    private StaffMapper staffMapper;

    @Resource
    private LoanApi loanApi;

    @Resource
    private BpmOALoanApi bpmOALoanApi;

    @Resource
    private StaffService staffService;

    @Override
    public Long createStaffSalary(StaffSalarySaveReqVO createReqVO) {

        // 校验当前月份工资是否存在
        Long count = staffSalaryMapper.selectCount(new LambdaQueryWrapperX<StaffSalaryDO>()
                .eq(StaffSalaryDO::getStaffId, createReqVO.getStaffId())
                .eq(StaffSalaryDO::getMonth, createReqVO.getMonth()));
        if (count > 0L) {
            throw exception(STAFF_SALARY_EXISTS);
        }

        // 插入
        StaffSalaryDO staffSalaryDO = BeanUtils.toBean(createReqVO, StaffSalaryDO.class);
        staffSalaryMapper.insert(staffSalaryDO);

        return staffSalaryDO.getId();
    }

    @Override
    public void updateStaffSalary(StaffSalarySaveReqVO updateReqVO) {
        // 校验存在
        validateStaffSalaryExists(updateReqVO.getId());
        // 更新
        StaffSalaryDO updateObj = BeanUtils.toBean(updateReqVO, StaffSalaryDO.class);
        staffSalaryMapper.updateById(updateObj);
    }

    @Override
    public void deleteStaffSalary(Long id) {
        // 校验存在
        validateStaffSalaryExists(id);
        // 删除
        staffSalaryMapper.deleteById(id);
    }

    private StaffSalaryDO validateStaffSalaryExists(Long id) {
        StaffSalaryDO staffSalaryDO = staffSalaryMapper.selectById(id);
        if (staffSalaryDO == null) {
            throw exception(STAFF_SALARY_NOT_EXISTS);
        }
        return staffSalaryDO;
    }

    @Override
    public StaffSalaryDO getStaffSalary(Long id) {
        return staffSalaryMapper.selectById(id);
    }

    @Override
    public PageResult<StaffSalaryRespVO> getStaffSalaryPage(StaffSalaryPageReqVO pageReqVO) {

        if (pageReqVO.getMonth() != null && pageReqVO.getStatus() == null) {
            return staffMapper.selectSalaryPage(pageReqVO);
        }else {
            return staffSalaryMapper.selectSalaryPage(pageReqVO);
        }
    }

    @Override
    public void updateStatus(StaffSalarySaveReqVO updateReqVO) {

        // 校验存在
        StaffSalaryDO staffSalaryDO = validateStaffSalaryExists(updateReqVO.getId());
        // 更新
        StaffSalaryDO updateObj = BeanUtils.toBean(updateReqVO, StaffSalaryDO.class);
        staffSalaryMapper.updateById(updateObj);

        // 判断是否有归还借支金额
        if (staffSalaryDO.getReturnAmount().compareTo(BigDecimal.ZERO) > 0) {
            // 更新借支管理中 人员的归还金额
            LoanDTO updateDO = new LoanDTO()
                    .setUserId(staffSalaryDO.getStaffId())
                    .setAmount(BigDecimal.ZERO)
                    .setReturnAmount(staffSalaryDO.getReturnAmount());
            loanApi.createLoan(updateDO);
        }
    }

    @Override
    public void calculateSalary(List<StaffAttendanceRecordDO> vo) {

        // 获取厂区考勤类型
        Integer type = vo.get(0).getType();
        // 获取厂区考勤月份
        String month = vo.get(0).getMonth();

        // 获取当前考勤月份得第一天
        LocalDate firstDayOfMonth = LocalDate.parse(month + "-01", DateTimeFormatter.ISO_LOCAL_DATE);

        LocalDate[] date = new LocalDate[2];
        if (type == 1) {

            date = new LocalDate[]{
                    firstDayOfMonth.minusMonths(1).withDayOfMonth(26),
                    firstDayOfMonth.withDayOfMonth(25)
            };
        }else if (type == 2) {

            date = new LocalDate[]{
                    firstDayOfMonth,
                    firstDayOfMonth.withDayOfMonth(firstDayOfMonth.lengthOfMonth())
            };
        }

        Map<Long, List<StaffAttendanceRecordDO>> recordMap = vo.stream()
                .collect(Collectors.groupingBy(StaffAttendanceRecordDO::getStaffId));

        // 获取厂区得员工信息
        List<StaffDO> staffDOS = staffService.getList(convertSet(vo, StaffAttendanceRecordDO::getStaffId));
        Map<Long, StaffDO> staffMap = convertMap(staffDOS, StaffDO::getId);

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

        // 获取员工剩余的 工资借支金额
        List<LoanDTO> sysLoanDOS = loanApi.getListByMonth(recordMap.keySet(), month).getCheckedData();
        Map<Long, BigDecimal> sysLoanMap = new HashMap<>();
        if (CollUtil.isNotEmpty(sysLoanDOS)) {
            sysLoanMap = sysLoanDOS.stream()
                    .collect(Collectors.toMap(LoanDTO::getUserId, LoanDTO::getRemainingAmount));
        }

        List<StaffSalaryDO> salaryDOS = new ArrayList<>();
        for (Map.Entry<Long, List<StaffAttendanceRecordDO>> entry : recordMap.entrySet()) {
            Long staffId = entry.getKey();
            StaffDO staffDO = staffMap.get(staffId);
            if (staffDO == null) {
                continue;
            }
            // 获取该员工的上班考勤记录
            List<StaffAttendanceRecordDO> items = entry.getValue().stream()
                    .filter(item -> item.getStatus() == 1)
                    .collect(Collectors.toList());

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

            // 计算当月的工作天数
            long days = ChronoUnit.DAYS.between(date[0], date[1]);
            int workDays = (int) days + 1;

            // 设置扣款明细  默认扣除保险 50元
            salaryDO.setDeductionItems(Collections.singletonList(
                        new DeductionItemsVO()
                                .setKey("insurance")
                                .setValue("50")
                    ));

            // 计算应发工资
            BigDecimal salary = staffDO.getSalary();
            if (salary != null) {  // 没有录入基本薪资时，不做计算

                if (salaryDO.getAttendanceDays() == workDays) {
                    // 设置应发工资
                    salaryDO.setPayableAmount(salary);
                    // 设置实发工资
                    salaryDO.setRealAmount(salary.subtract(BigDecimal.valueOf(50)));
                } else {
                    BigDecimal payableAmount = salary.divide(BigDecimal.valueOf(workDays), 0, RoundingMode.DOWN)
                            .multiply(BigDecimal.valueOf(salaryDO.getAttendanceDays()));
                    // 设置应发工资
                    salaryDO.setPayableAmount(payableAmount);
                    // 设置实发工资
                    salaryDO.setRealAmount(payableAmount.subtract(BigDecimal.valueOf(50)));
                }

                // 设置薪资主体数据
                if (staffDO.getSalarySubject() != null) {
                    salaryDO.setSalarySubjectItems(Collections.singletonList(
                            new SalarySubjectItemsVO()
                                    .setSalarySubjectId(staffDO.getSalarySubject())
                                    .setAmount(salaryDO.getPayableAmount())
                    ));
                }
            }

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

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

    @Override
    public void updateIsGrant(Collection<Long> ids, Integer isGrant) {

        List<StaffSalaryDO> salaryDOS = ids.stream()
                .map(id -> new StaffSalaryDO()
                        .setId(id)
                        .setIsGrant(isGrant))
                .collect(Collectors.toList());

        // 更新
        staffSalaryMapper.updateBatch(salaryDOS);
    }

    @Override
    public SalaryTotalVO getStaffSalaryTotal(StaffSalaryPageReqVO pageReqVO) {

        // 获取实发工资的合计
        SalaryTotalVO salaryTotalVO = staffSalaryMapper.selectSalaryTotal(pageReqVO);

        // 获取主体薪资的合计
        List<StaffSalaryRespVO> salarySubjectItemsVO = staffSalaryMapper.selectListSalarySubjectItems(pageReqVO);
        //直接从数据库取出来的List<Reimbursement> 实际上是List<LinkedHashMap>类型 所以不能直接遍历
        //将list再次转为json串，然后由json串再转为list
        String json = JsonUtils.toJsonString(salarySubjectItemsVO);
        salarySubjectItemsVO = JsonUtils.parseArray(json, StaffSalaryRespVO.class);

        if (CollUtil.isNotEmpty(salarySubjectItemsVO)) {
            Map<Integer, SalarySubjectItemsVO> sumMap = salarySubjectItemsVO.stream()
                    .filter(Objects::nonNull)
                    .map(StaffSalaryRespVO::getSalarySubjectItems)
                    .flatMap(List::stream)
                    .collect(Collectors.toMap(
                            SalarySubjectItemsVO::getSalarySubjectId,
                            vo -> new SalarySubjectItemsVO()
                                    .setSalarySubjectId(vo.getSalarySubjectId())
                                    .setAmount(vo.getAmount()),
                            (vo1, vo2) -> {
                                vo1.setAmount(vo1.getAmount().add(vo2.getAmount()));
                                return vo1;
                            }
                    ));
            salaryTotalVO.setSalarySubjectItems(new ArrayList<>(sumMap.values()));
        }

        return salaryTotalVO;
    }
}