package com.ylx.attendanceconfig.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.ylx.attendanceconfig.domain.AttendanceDeductionRule;
import com.ylx.attendanceconfig.domain.AttendanceRule;
import com.ylx.attendanceconfig.domain.dto.AttendanceDeductionRuleDTO;
import com.ylx.attendanceconfig.domain.dto.AttendanceRuleAddDTO;
import com.ylx.attendanceconfig.domain.vo.AttendanceDeductionRuleVO;
import com.ylx.attendanceconfig.domain.vo.AttendanceRuleDetailVO;
import com.ylx.attendanceconfig.mapper.AttendanceDeductionRuleMapper;
import com.ylx.attendanceconfig.mapper.AttendanceRuleMapper;
import com.ylx.attendanceconfig.service.AttendanceConfigService;
import com.ylx.common.exception.ServiceException;
import com.ylx.common.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class AttendanceConfigServiceImpl implements AttendanceConfigService {

    @Resource
    private AttendanceRuleMapper attendanceRuleMapper;

    @Resource
    private AttendanceDeductionRuleMapper attendanceDeductionRuleMapper;

    private static final int NOT_DELETE = 0;
    private static final int DELETE = 1;
    private static final int ENABLED = 1;
    private static final int DISABLED = 0;
    private static final int EARLY_LEAVE_DEDUCTION = 1;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addAttendanceRule(AttendanceRuleAddDTO dto) {
        checkRuleParam(dto);

        AttendanceRule rule = new AttendanceRule();
        rule.setRuleName(StrUtil.blankToDefault(StrUtil.trim(dto.getRuleName()), "默认考勤规则"));
        rule.setBasicWorkHours(dto.getBasicWorkHours());
        rule.setWorkDurationRuleEnabled(dto.getWorkDurationRuleEnabled());
        rule.setStatus(ObjectUtil.defaultIfNull(dto.getStatus(), ENABLED));
        rule.setIsDelete(NOT_DELETE);
        rule.setCreateBy(SecurityUtils.getUsername());
        rule.setUpdateBy(SecurityUtils.getUsername());

        int insertRuleResult = attendanceRuleMapper.insert(rule);
        if (insertRuleResult <= 0 || ObjectUtil.isNull(rule.getId())) {
            throw new ServiceException("新增考勤规则失败");
        }

        if (dto.getWorkDurationRuleEnabled() == ENABLED) {
            List<AttendanceDeductionRuleDTO> sortedRules = sortDeductionRules(dto.getDeductionRules());
            for (int i = 0; i < sortedRules.size(); i++) {
                AttendanceDeductionRuleDTO deductionDto = sortedRules.get(i);
                AttendanceDeductionRule deductionRule = new AttendanceDeductionRule();
                deductionRule.setRuleId(rule.getId());
                deductionRule.setRuleType(ObjectUtil.defaultIfNull(deductionDto.getRuleType(), EARLY_LEAVE_DEDUCTION));
                deductionRule.setStartMinutes(deductionDto.getStartMinutes());
                deductionRule.setEndMinutes(deductionDto.getEndMinutes());
                deductionRule.setDeductAmount(deductionDto.getDeductAmount());
                deductionRule.setSortOrder(i + 1);
                deductionRule.setIsDelete(NOT_DELETE);
                deductionRule.setCreateBy(SecurityUtils.getUsername());
                deductionRule.setUpdateBy(SecurityUtils.getUsername());

                int insertDeductionResult = attendanceDeductionRuleMapper.insert(deductionRule);
                if (insertDeductionResult <= 0) {
                    throw new ServiceException("新增考勤扣款区间失败");
                }
            }
        }
    }

    @Override
    public List<AttendanceRuleDetailVO> listAttendanceRules() {
        List<AttendanceRule> ruleList = attendanceRuleMapper.selectList(new LambdaQueryWrapper<AttendanceRule>()
                .orderByDesc(AttendanceRule::getCreateTime));
        return ruleList.stream()
                .map(this::toDetailVO)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteAttendanceRule(Long id) {
        if (ObjectUtil.isNull(id) || id <= 0) {
            throw new ServiceException("考勤规则ID不正确");
        }
        AttendanceRule rule = attendanceRuleMapper.selectOne(new LambdaQueryWrapper<AttendanceRule>()
                .eq(AttendanceRule::getId, id)
                .eq(AttendanceRule::getIsDelete, NOT_DELETE));
        if (ObjectUtil.isNull(rule)) {
            throw new ServiceException("考勤规则不存在或已删除");
        }

        String username = SecurityUtils.getUsername();
        LocalDateTime now = LocalDateTime.now();
        int updateRuleResult = attendanceRuleMapper.update(null, new LambdaUpdateWrapper<AttendanceRule>()
                .eq(AttendanceRule::getId, id)
                .eq(AttendanceRule::getIsDelete, NOT_DELETE)
                .set(AttendanceRule::getIsDelete, DELETE)
                .set(AttendanceRule::getUpdateBy, username)
                .set(AttendanceRule::getUpdateTime, now));
        if (updateRuleResult <= 0) {
            throw new ServiceException("删除考勤规则失败");
        }

        attendanceDeductionRuleMapper.update(null, new LambdaUpdateWrapper<AttendanceDeductionRule>()
                .eq(AttendanceDeductionRule::getRuleId, id)
                .eq(AttendanceDeductionRule::getIsDelete, NOT_DELETE)
                .set(AttendanceDeductionRule::getIsDelete, DELETE)
                .set(AttendanceDeductionRule::getUpdateBy, username)
                .set(AttendanceDeductionRule::getUpdateTime, now));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteDeductionRule(Long id) {
        if (ObjectUtil.isNull(id) || id <= 0) {
            throw new ServiceException("扣款区间ID不正确");
        }
        AttendanceDeductionRule deductionRule = attendanceDeductionRuleMapper.selectOne(
                new LambdaQueryWrapper<AttendanceDeductionRule>()
                        .eq(AttendanceDeductionRule::getId, id));
        if (ObjectUtil.isNull(deductionRule)) {
            throw new ServiceException("扣款区间不存在或已删除");
        }

        int updateResult = attendanceDeductionRuleMapper.deleteById(id);
        if (updateResult <= 0) {
            throw new ServiceException("删除扣款区间失败");
        }
    }

    private void checkRuleParam(AttendanceRuleAddDTO dto) {
        if (ObjectUtil.isNull(dto)) {
            throw new ServiceException("考勤规则参数不能为空");
        }
        if (ObjectUtil.isNull(dto.getBasicWorkHours()) || dto.getBasicWorkHours().compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("基本工作时长必须大于0");
        }
        if (dto.getWorkDurationRuleEnabled() != ENABLED && dto.getWorkDurationRuleEnabled() != DISABLED) {
            throw new ServiceException("工作时长规则值不正确");
        }
        if (ObjectUtil.isNotNull(dto.getStatus()) && dto.getStatus() != ENABLED && dto.getStatus() != DISABLED) {
            throw new ServiceException("考勤规则状态值不正确");
        }
        if (dto.getWorkDurationRuleEnabled() == ENABLED) {
            checkDeductionRules(dto.getDeductionRules());
        }
    }

    private void checkDeductionRules(List<AttendanceDeductionRuleDTO> deductionRules) {
        if (CollectionUtil.isEmpty(deductionRules)) {
            throw new ServiceException("启用工作时长规则时，扣款区间不能为空");
        }
        List<AttendanceDeductionRuleDTO> sortedRules = sortDeductionRules(deductionRules);
        int expectedStartMinutes = 1;
        for (AttendanceDeductionRuleDTO deductionRule : sortedRules) {
            if (ObjectUtil.defaultIfNull(deductionRule.getRuleType(), EARLY_LEAVE_DEDUCTION) != EARLY_LEAVE_DEDUCTION) {
                throw new ServiceException("扣款规则类型不正确");
            }
            if (ObjectUtil.isNull(deductionRule.getStartMinutes()) || deductionRule.getStartMinutes() <= 0) {
                throw new ServiceException("开始分钟数必须大于0");
            }
            if (ObjectUtil.isNull(deductionRule.getEndMinutes()) || deductionRule.getEndMinutes() < deductionRule.getStartMinutes()) {
                throw new ServiceException("结束分钟数不能小于开始分钟数");
            }
            if (ObjectUtil.isNull(deductionRule.getDeductAmount()) || deductionRule.getDeductAmount().compareTo(BigDecimal.ZERO) < 0) {
                throw new ServiceException("扣款金额不能小于0");
            }
            if (deductionRule.getStartMinutes() != expectedStartMinutes) {
                throw new ServiceException("扣款区间必须从1分钟开始且连续");
            }
            expectedStartMinutes = deductionRule.getEndMinutes() + 1;
        }
    }

    private List<AttendanceDeductionRuleDTO> sortDeductionRules(List<AttendanceDeductionRuleDTO> deductionRules) {
        return deductionRules.stream()
                .sorted(Comparator.comparing(AttendanceDeductionRuleDTO::getStartMinutes))
                .collect(Collectors.toList());
    }

    private AttendanceRuleDetailVO toDetailVO(AttendanceRule rule) {
        AttendanceRuleDetailVO vo = new AttendanceRuleDetailVO();
        BeanUtil.copyProperties(rule, vo);
        vo.setDeductionRules(getDeductionRuleVOList(rule.getId()));
        return vo;
    }

    private List<AttendanceDeductionRuleVO> getDeductionRuleVOList(Long ruleId) {
        List<AttendanceDeductionRule> deductionRules = attendanceDeductionRuleMapper.selectList(
                new LambdaQueryWrapper<AttendanceDeductionRule>()
                        .eq(AttendanceDeductionRule::getRuleId, ruleId)
                        .orderByAsc(AttendanceDeductionRule::getSortOrder)
                        .orderByAsc(AttendanceDeductionRule::getStartMinutes));
        return deductionRules.stream().map(entity -> {
            AttendanceDeductionRuleVO vo = new AttendanceDeductionRuleVO();
            BeanUtil.copyProperties(entity, vo);
            return vo;
        }).collect(Collectors.toList());
    }
}
