package com.ylx.massage.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ylx.attendanceconfig.domain.AttendanceDeductionRule;
import com.ylx.attendanceconfig.domain.AttendanceRule;
import com.ylx.attendanceconfig.mapper.AttendanceDeductionRuleMapper;
import com.ylx.attendanceconfig.mapper.AttendanceRuleMapper;
import com.ylx.common.exception.ServiceException;
import com.ylx.common.utils.DateUtils;
import com.ylx.common.utils.StringUtils;
import com.ylx.massage.domain.MerchantDailyAttendance;
import com.ylx.massage.domain.dto.MerchantDailyAttendanceQueryDTO;
import com.ylx.massage.mapper.MerchantDailyAttendanceMapper;
import com.ylx.massage.service.MerchantDailyAttendanceService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 商户每日考勤统计表(MerchantDailyAttendance)表服务实现类
 *
 * @author makejava
 * @since 2026-06-05 11:16:10
 */
@Service("merchantDailyAttendanceService")
public class MerchantDailyAttendanceServiceImpl extends ServiceImpl<MerchantDailyAttendanceMapper, MerchantDailyAttendance> implements MerchantDailyAttendanceService {

    private static final int NOT_DELETE = 0;

    /**
     * 正常考勤状态
     */
    private static final int ATTENDANCE_NORMAL = 1;

    /**
     * 异常考勤状态
     */
    private static final int ATTENDANCE_ABNORMAL = 2;
    private static final int ENABLED = 1;
    private static final int EARLY_LEAVE_DEDUCTION = 1;

    @Resource
    private AttendanceRuleMapper attendanceRuleMapper;

    @Resource
    private AttendanceDeductionRuleMapper attendanceDeductionRuleMapper;

    @Override
    public Page<MerchantDailyAttendance> queryDailyAttendance(Page<MerchantDailyAttendance> page, MerchantDailyAttendanceQueryDTO query) {
        MerchantDailyAttendanceQueryDTO condition = query == null ? new MerchantDailyAttendanceQueryDTO() : query;
        List<MerchantDailyAttendance> attendanceList = list(buildBaseQuery(condition));
        AttendanceRule enabledRule = getEnabledAttendanceRule();
        List<AttendanceDeductionRule> deductionRules = getDeductionRules(enabledRule);

        /**
         * 构建每日考勤统计列表
         */
        List<MerchantDailyAttendance> summaryList = buildDailySummaryList(attendanceList, enabledRule, deductionRules);
        if (condition.getAttendanceStatus() != null) {
            summaryList = summaryList.stream()
                    .filter(item -> condition.getAttendanceStatus().equals(item.getAttendanceStatus()))
                    .collect(Collectors.toList());
        }

        long current = page.getCurrent() <= 0 ? 1 : page.getCurrent();
        long size = page.getSize() <= 0 ? 10 : page.getSize();
        int fromIndex = (int) Math.min((current - 1) * size, summaryList.size());
        int toIndex = (int) Math.min(fromIndex + size, summaryList.size());

        Page<MerchantDailyAttendance> resultPage = new Page<>(current, size);
        resultPage.setTotal(summaryList.size());
        resultPage.setRecords(summaryList.subList(fromIndex, toIndex));
        return resultPage;
    }

    @Override
    public List<MerchantDailyAttendance> queryDailyWorkTime(Integer merchantId, String attendanceDate) {
        if (merchantId == null) {
            throw new ServiceException("商户ID不能为空");
        }
        Date dayStart = parseDateStart(attendanceDate, "考勤日期不能为空");
        Date nextDayStart = addDays(dayStart, 1);

        List<MerchantDailyAttendance> records = list(new LambdaQueryWrapper<MerchantDailyAttendance>()
                .eq(MerchantDailyAttendance::getMerchantId, merchantId)
                .ge(MerchantDailyAttendance::getAttendanceDate, dayStart)
                .lt(MerchantDailyAttendance::getAttendanceDate, nextDayStart)
                .orderByAsc(MerchantDailyAttendance::getAttendanceStartTime));

        records.forEach(item -> item.setTotalWorkDurationText(formatDuration(defaultMinutes(item.getTotalWorkMinutes()))));
        return records;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void refreshYesterdayDeductionAmount() {
        Date yesterday = addDays(DateUtils.getNowDate(), -1);
        refreshDeductionAmountByDate(yesterday);
    }

    /**
     * 构建基础查询条件。
     * @param query 查询实体
     * @return LambdaQueryWrapper<MerchantDailyAttendance> 基础查询条件
     */
    private LambdaQueryWrapper<MerchantDailyAttendance> buildBaseQuery(MerchantDailyAttendanceQueryDTO query) {
        LambdaQueryWrapper<MerchantDailyAttendance> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.isNotBlank(query.getMerchantName()), MerchantDailyAttendance::getMerchantName, query.getMerchantName())
                .orderByDesc(MerchantDailyAttendance::getAttendanceDate);
        //考勤状态
        /*if (query.getAttendanceStatus() != null) {
            wrapper.eq(MerchantDailyAttendance::getAttendanceStatus, query.getAttendanceStatus());
        }*/
        //开始日期
        if (StringUtils.isNotBlank(query.getBeginDate())) {
            wrapper.ge(MerchantDailyAttendance::getAttendanceDate, parseDateStart(query.getBeginDate(), "开始日期格式不正确"));
        }
        //结束日期
        if (StringUtils.isNotBlank(query.getEndDate())) {
            wrapper.lt(MerchantDailyAttendance::getAttendanceDate, addDays(parseDateStart(query.getEndDate(), "结束日期格式不正确"), 1));
        }
        return wrapper;
    }

    /**
     * 构建每日考勤统计列表
     * @param attendanceList 考勤记录列表
     * @param enabledRule 有效考勤规则
     * @param deductionRules 早退扣款规则列表
     * @return List<MerchantDailyAttendance>
     */
    private List<MerchantDailyAttendance> buildDailySummaryList(List<MerchantDailyAttendance> attendanceList, AttendanceRule enabledRule, List<AttendanceDeductionRule> deductionRules) {
        Map<String, List<MerchantDailyAttendance>> groupMap = attendanceList.stream()
                .filter(item -> item.getMerchantId() != null && item.getAttendanceDate() != null)
                .collect(Collectors.groupingBy(this::buildDailyGroupKey, LinkedHashMap::new, Collectors.toList()));

        List<MerchantDailyAttendance> summaryList = new ArrayList<>();
        for (List<MerchantDailyAttendance> groupRecords : groupMap.values()) {
            MerchantDailyAttendance first = groupRecords.get(0);
            int totalMinutes = groupRecords.stream()
                    .map(MerchantDailyAttendance::getTotalWorkMinutes)
                    .filter(Objects::nonNull)
                    .mapToInt(Integer::intValue)
                    .sum();

            MerchantDailyAttendance summary = new MerchantDailyAttendance();
            BeanUtils.copyProperties(first, summary);
            summary.setTotalWorkMinutes(totalMinutes);
            summary.setTotalWorkDurationText(formatDuration(totalMinutes));
            summary.setAttendanceStatus(calculateAttendanceStatus(totalMinutes, enabledRule, first.getAttendanceStatus()));
            summary.setDeductionAmount(calculateDeductionAmount(totalMinutes, enabledRule, deductionRules).doubleValue());
            summaryList.add(summary);
        }

        summaryList.sort(Comparator
                .comparing(MerchantDailyAttendance::getAttendanceDate, Comparator.nullsLast(Date::compareTo)).reversed()
                .thenComparing(MerchantDailyAttendance::getMerchantId, Comparator.nullsLast(Integer::compareTo)));
        return summaryList;
    }

    private void refreshDeductionAmountByDate(Date attendanceDate) {
        Date dayStart = startOfDay(attendanceDate);
        Date nextDayStart = addDays(dayStart, 1);
        List<MerchantDailyAttendance> records = list(new LambdaQueryWrapper<MerchantDailyAttendance>()
                .ge(MerchantDailyAttendance::getAttendanceDate, dayStart)
                .lt(MerchantDailyAttendance::getAttendanceDate, nextDayStart)
                .eq(MerchantDailyAttendance::getIsDelete, NOT_DELETE));
        if (records.isEmpty()) {
            return;
        }

        AttendanceRule enabledRule = getEnabledAttendanceRule();
        List<AttendanceDeductionRule> deductionRules = getDeductionRules(enabledRule);
        List<MerchantDailyAttendance> summaryList = buildDailySummaryList(records, enabledRule, deductionRules);
        for (MerchantDailyAttendance summary : summaryList) {
            update(null, new LambdaUpdateWrapper<MerchantDailyAttendance>()
                    .eq(MerchantDailyAttendance::getMerchantId, summary.getMerchantId())
                    .ge(MerchantDailyAttendance::getAttendanceDate, startOfDay(summary.getAttendanceDate()))
                    .lt(MerchantDailyAttendance::getAttendanceDate, addDays(startOfDay(summary.getAttendanceDate()), 1))
                    .eq(MerchantDailyAttendance::getIsDelete, NOT_DELETE)
                    .set(MerchantDailyAttendance::getAttendanceStatus, summary.getAttendanceStatus())
                    .set(MerchantDailyAttendance::getDeductionAmount, summary.getDeductionAmount())
                    .set(MerchantDailyAttendance::getUpdateTime, java.time.LocalDateTime.now()));
        }
    }

    /**
     * 获取当前生效的考勤规则
     * @return AttendanceRule
     */
    private AttendanceRule getEnabledAttendanceRule() {
        return attendanceRuleMapper.selectOne(new LambdaQueryWrapper<AttendanceRule>()
                .eq(AttendanceRule::getStatus, ENABLED)
                .eq(AttendanceRule::getWorkDurationRuleEnabled, ENABLED)
                .orderByDesc(AttendanceRule::getCreateTime)
                .last("LIMIT 1"));
    }

    /**
     * 获取早退扣款规则
     * @param rule
     * @return List<AttendanceDeductionRule>
     */
    private List<AttendanceDeductionRule> getDeductionRules(AttendanceRule rule) {
        if (rule == null || rule.getId() == null) {
            return new ArrayList<>();
        }
        return attendanceDeductionRuleMapper.selectList(new LambdaQueryWrapper<AttendanceDeductionRule>()
                .eq(AttendanceDeductionRule::getRuleId, rule.getId())
                .eq(AttendanceDeductionRule::getRuleType, EARLY_LEAVE_DEDUCTION)
                .orderByAsc(AttendanceDeductionRule::getCreateTime));
    }

    /**
     * 计算考勤状态
     * @param totalMinutes 工作时长，单位：分钟
     * @param rule 考勤规则
     * @param fallbackStatus 回退状态
     * @return 考勤状态
     */
    private Integer calculateAttendanceStatus(int totalMinutes, AttendanceRule rule, Integer fallbackStatus) {
        if (rule == null || rule.getBasicWorkHours() == null) {
            return fallbackStatus == null ? ATTENDANCE_NORMAL : fallbackStatus;
        }
        return totalMinutes >= getBasicWorkMinutes(rule) ? ATTENDANCE_NORMAL : ATTENDANCE_ABNORMAL;
    }

    /**
     * 计算扣款金额
     * @param totalMinutes
     * @param rule
     * @param deductionRules
     * @return BigDecimal 扣款金额，单位：元
     */
    private BigDecimal calculateDeductionAmount(int totalMinutes, AttendanceRule rule, List<AttendanceDeductionRule> deductionRules) {
        if (rule == null || rule.getBasicWorkHours() == null || deductionRules == null || deductionRules.isEmpty()) {
            return BigDecimal.ZERO;
        }
        int lackMinutes = getBasicWorkMinutes(rule) - totalMinutes;
        if (lackMinutes <= 0) {
            return BigDecimal.ZERO;
        }
        return deductionRules.stream()
                .filter(ruleItem -> ruleItem.getStartMinutes() != null && ruleItem.getEndMinutes() != null)
                .filter(ruleItem -> lackMinutes >= ruleItem.getStartMinutes() && lackMinutes <= ruleItem.getEndMinutes())
                .map(AttendanceDeductionRule::getDeductAmount)
                .filter(Objects::nonNull)
                .findFirst()
                .orElse(BigDecimal.ZERO);
    }

    /**
     * 获取基本工作时长
     *
     * @param rule 考勤规则
     * @return 基本工作时长，单位：分钟
     */
    private int getBasicWorkMinutes(AttendanceRule rule) {
        return rule.getBasicWorkHours()
                .multiply(BigDecimal.valueOf(60))
                .setScale(0, RoundingMode.HALF_UP)
                .intValue();
    }

    /**
     * 构建每日考勤统计分组键
     * @param attendance 考勤记录
     * @return String 分组键
     */
    private String buildDailyGroupKey(MerchantDailyAttendance attendance) {
        return attendance.getMerchantId() + "_" + DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, attendance.getAttendanceDate());
    }

    /**
     * 格式化工作时长
     * @param minutes 工作时长，单位：分钟
     * @return 格式化后的字符串时长
     */
    private String formatDuration(Integer minutes) {
        int totalMinutes = defaultMinutes(minutes);
        return totalMinutes / 60 + "小时" + String.format("%02d", totalMinutes % 60) + "分钟";
    }

    private int defaultMinutes(Integer minutes) {
        return minutes == null ? 0 : minutes;
    }

    /**
     * 解析日期字符串为日期对象
     * @param date
     * @param errorMessage
     * @return
     */
    private Date parseDateStart(String date, String errorMessage) {
        if (StringUtils.isBlank(date)) {
            throw new ServiceException(errorMessage);
        }
        Date parsedDate = DateUtils.parseDate(date.trim());
        if (parsedDate == null) {
            throw new ServiceException(errorMessage);
        }
        return startOfDay(parsedDate);
    }

    private Date startOfDay(Date date) {
        LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
    }

    private Date addDays(Date date, int amount) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DAY_OF_MONTH, amount);
        return calendar.getTime();
    }
}
