package org.dromara.project.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.dromara.common.core.constant.DateConstant;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.enums.FormatsType;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.project.domain.BusAttendance;
import org.dromara.project.domain.BusConstructionUser;
import org.dromara.project.domain.BusProjectTeamMember;
import org.dromara.project.domain.enums.BusAttendanceClockStatusEnum;
import org.dromara.project.domain.enums.BusAttendanceCommuterEnum;
import org.dromara.project.domain.enums.BusAttendanceStatusEnum;
import org.dromara.project.domain.dto.attendance.BusAttendanceMonthByUserIdReq;
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryReq;
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryTwoWeekReq;
import org.dromara.project.domain.vo.attendance.BusAttendanceClockDateForTwoWeekVo;
import org.dromara.project.domain.vo.attendance.BusAttendanceListByDay;
import org.dromara.project.domain.vo.attendance.BusAttendanceMonthByUserIdVo;
import org.dromara.project.domain.vo.attendance.BusAttendanceVo;
import org.dromara.project.mapper.BusAttendanceMapper;
import org.dromara.project.service.*;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.YearMonth;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 考勤Service业务层处理
 *
 * @author lcj
 * @date 2025-04-07
 */
@Service
public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, BusAttendance>
    implements IBusAttendanceService {

    @Resource
    private IBusProjectService projectService;

    @Resource
    private IBusProjectTeamMemberService projectTeamMemberService;

    @Resource
    private IBusConstructionUserService constructionUserService;

    @Resource
    private IBusConstructionBlacklistService constructionBlacklistService;

    // 出勤状态（正常、迟到、早退）
    private static final Set<String> ATTENDANCE_STATUS = new HashSet<>(Arrays.asList(BusAttendanceClockStatusEnum.NORMAL.getValue(),
        BusAttendanceClockStatusEnum.LATE.getValue(), BusAttendanceClockStatusEnum.LEAVEEARLY.getValue()));

    /**
     * 查询考勤
     *
     * @param id 主键
     * @return 考勤
     */
    @Override
    public BusAttendanceVo queryById(Long id) {
        BusAttendance attendance = this.getById(id);
        if (attendance == null) {
            throw new ServiceException("考勤信息不存在", HttpStatus.NOT_FOUND);
        }
        return this.getVo(attendance);
    }

    /**
     * 分页查询考勤列表
     *
     * @param req       查询条件
     * @param pageQuery 分页参数
     * @return 考勤分页列表
     */
    @Override
    public TableDataInfo<BusAttendanceVo> queryPageList(BusAttendanceQueryReq req, PageQuery pageQuery) {
        Page<BusAttendance> result = this.page(pageQuery.build(), buildQueryWrapper(req));
        return TableDataInfo.build(this.getVoPage(result));
    }

    /**
     * 查询两周内的考勤列表
     *
     * @param req 查询条件
     * @return 考勤列表
     */
    @Override
    public List<BusAttendanceClockDateForTwoWeekVo> listClockDateForTwoWeek(BusAttendanceQueryTwoWeekReq req) {
        Long projectId = req.getProjectId();
        if (projectService.getById(projectId) == null) {
            throw new ServiceException("项目信息不存在", HttpStatus.NOT_FOUND);
        }
        // 1. 处理日期区间
        Date clockDate = req.getClockDate();
        LocalDate startLocal;
        LocalDate endLocal;
        if (clockDate != null) {
            // 检查 clockDate 不能大于当前日期
            if (DateUtil.compare(clockDate, new Date()) > 0) {
                throw new ServiceException("日期不能大于当前日期", HttpStatus.BAD_REQUEST);
            }
            // 以传入的 clockDate 为结束日期
            endLocal = clockDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        } else {
            // 如果未传入，则以当前日期为结束日期
            endLocal = LocalDate.now();
        }
        // 计算开始日期为结束日期减两周
        startLocal = endLocal.minusWeeks(2);
        // 如果数据库中的 clockDate 只存储日期（时分秒置为 00:00:00），直接转换即可
        Date startDate = Date.from(startLocal.atStartOfDay(ZoneId.systemDefault()).toInstant());
        Date endDate = Date.from(endLocal.atStartOfDay(ZoneId.systemDefault()).toInstant());
        // 构造查询条件 clockDate 在 [startDate, endDate] 区间内
        LambdaQueryWrapper<BusAttendance> lqw = new LambdaQueryWrapper<>();
        lqw.eq(BusAttendance::getProjectId, projectId)
            .eq(StringUtils.isNotEmpty(req.getUserName()), BusAttendance::getUserName, req.getUserName())
            .between(BusAttendance::getClockDate, startDate, endDate)
            .orderByDesc(BusAttendance::getClockDate);
        // 获取黑名单用户列表
        List<Long> blackList = constructionBlacklistService.queryIdListByProjectId(projectId);
        // 构建查询用户相关信息查询条件
        List<Long> userIdList = constructionUserService.lambdaQuery()
            .eq(BusConstructionUser::getProjectId, projectId)
            .eq(req.getTeamId() != null, BusConstructionUser::getTeamId, req.getTeamId())
            .eq(req.getTypeOfWork() != null, BusConstructionUser::getTypeOfWork, req.getTypeOfWork())
            .notIn(CollUtil.isNotEmpty(blackList), BusConstructionUser::getId, blackList)
            .list().stream().map(BusConstructionUser::getId).toList();
        lqw.in(CollUtil.isNotEmpty(userIdList), BusAttendance::getUserId, userIdList);
        Map<Date, List<BusAttendance>> dateListMap = this.list(lqw)
            .stream().collect(Collectors.groupingBy(BusAttendance::getClockDate));
        // 遍历每个日期，计算考勤状态
        List<BusAttendanceClockDateForTwoWeekVo> respList = new ArrayList<>();
        // 遍历从两周前到今天的所有日期
        for (LocalDate localDate = startLocal; !localDate.isAfter(endLocal); localDate = localDate.plusDays(1)) {
            BusAttendanceClockDateForTwoWeekVo resp = new BusAttendanceClockDateForTwoWeekVo();
            // 转换为 Date 类型（时分秒为 00:00:00）
            Date currentDate = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
            resp.setClockDate(currentDate);
            int attendance = 0;
            int halfAttendance = 0;
            int absenteeism = 0;
            int leave = 0;
            // 获取当天的考勤记录（可能为 null）
            List<BusAttendance> attendanceList = dateListMap.getOrDefault(currentDate, Collections.emptyList());
            // 按用户分组考勤记录
            Map<Long, List<BusAttendance>> userAttendanceMap = attendanceList.stream()
                .collect(Collectors.groupingBy(BusAttendance::getUserId));
            if (CollUtil.isNotEmpty(userAttendanceMap)) {
                for (List<BusAttendance> userAttendanceList : userAttendanceMap.values()) {
                    String clockInStatus = null;
                    String clockOutStatus = null;
                    String clockAllDayStatus = null;
                    // 遍历同一用户的考勤记录，分别获取上下班状态
                    for (BusAttendance a : userAttendanceList) {
                        if (BusAttendanceCommuterEnum.CLOCKIN.getValue().equals(a.getCommuter())) {
                            clockInStatus = a.getClockStatus();
                        } else if (BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(a.getCommuter())) {
                            clockOutStatus = a.getClockStatus();
                        } else if (BusAttendanceCommuterEnum.ALLDAY.getValue().equals(a.getCommuter())) {
                            clockAllDayStatus = a.getClockStatus();
                        }
                    }
                    // 统计考勤状态
                    if (BusAttendanceClockStatusEnum.LEAVE.getValue().equals(clockAllDayStatus)) {
                        leave++;
                    } else if ((BusAttendanceClockStatusEnum.NORMAL.getValue().equals(clockInStatus)
                        || BusAttendanceClockStatusEnum.REISSUE.getValue().equals(clockInStatus))
                        && (BusAttendanceClockStatusEnum.NORMAL.getValue().equals(clockOutStatus)
                        || BusAttendanceClockStatusEnum.REISSUE.getValue().equals(clockOutStatus))) {
                        attendance++;
                    } else if (BusAttendanceClockStatusEnum.UNCLOCK.getValue().equals(clockInStatus)
                        && BusAttendanceClockStatusEnum.UNCLOCK.getValue().equals(clockOutStatus)) {
                        absenteeism++;
                    } else if (BusAttendanceClockStatusEnum.UNCLOCK.getValue().equals(clockInStatus)
                        || BusAttendanceClockStatusEnum.UNCLOCK.getValue().equals(clockOutStatus)) {
                        halfAttendance++;
                    }
                }
            }
            resp.setAttendance(attendance);
            resp.setHalfAttendance(halfAttendance);
            resp.setAbsenteeism(absenteeism);
            resp.setLeave(leave);
            respList.add(resp);
        }
        // 按打卡日期正序排列
        respList.sort(Comparator.comparing(BusAttendanceClockDateForTwoWeekVo::getClockDate));
        return respList;
    }

    /**
     * 查询用户每月考勤列表
     *
     * @param req 查询条件
     * @return 考勤列表
     */
    @Override
    public List<BusAttendanceMonthByUserIdVo> listAttendanceMonthListByUserId(BusAttendanceMonthByUserIdReq req) {
        Long userId = req.getUserId();
        String clockMonth = req.getClockMonth();
        if (constructionUserService.getById(userId) == null) {
            throw new ServiceException("施工人员信息不存在", HttpStatus.NOT_FOUND);
        }
        // 解析月份
        YearMonth yearMonth;
        if (StringUtils.isNotBlank(clockMonth)) {
            // 校验月份格式
            if (!DateConstant.YEAR_MONTH_PATTERN.matcher(clockMonth).matches()) {
                throw new ServiceException("月份格式不正确", HttpStatus.BAD_REQUEST);
            }
            yearMonth = YearMonth.parse(clockMonth);
        } else {
            // 如果月份为空，则默认查询当前月份
            yearMonth = YearMonth.now();
        }
        // 计算当月第一天 / 最后一天
        Date start = DateUtils.toDate(yearMonth.atDay(1));
        Date end = DateUtils.toDate(yearMonth.atEndOfMonth());
        // 查询当月考勤记录
        Map<Date, List<BusAttendance>> dateListMap = this.lambdaQuery()
            .eq(BusAttendance::getUserId, userId)
            .between(BusAttendance::getClockDate, start, end)
            .list()
            .stream().collect(Collectors.groupingBy(BusAttendance::getClockDate));
        // 遍历每天，计算考勤状态
        List<BusAttendanceMonthByUserIdVo> respList = new ArrayList<>();
        dateListMap.forEach((date, attendanceList) -> {
            BusAttendanceMonthByUserIdVo resp = new BusAttendanceMonthByUserIdVo();
            resp.setId(userId);
            resp.setClockDate(date);
            List<BusAttendanceListByDay> attendanceListByDayList = new ArrayList<>();
            String clockInStatus = null;
            String clockOutStatus = null;
            String clockAllDayStatus = null;
            String status;
            for (BusAttendance attendance : attendanceList) {
                // 获取考勤记录
                BusAttendanceListByDay day = BusAttendanceListByDay.build(attendance);
                attendanceListByDayList.add(day);
                // 获取上下班状态
                if (BusAttendanceCommuterEnum.CLOCKIN.getValue().equals(attendance.getCommuter())) {
                    clockInStatus = attendance.getClockStatus();
                } else if (BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(attendance.getCommuter())) {
                    clockOutStatus = attendance.getClockStatus();
                } else {
                    clockAllDayStatus = attendance.getClockStatus();
                }
            }
            // 统计当天考勤状态
            if (BusAttendanceClockStatusEnum.LEAVE.getValue().equals(clockAllDayStatus)) {
                return;
            } else if (BusAttendanceClockStatusEnum.NORMAL.getValue().equals(clockInStatus)
                && BusAttendanceClockStatusEnum.NORMAL.getValue().equals(clockOutStatus)) {
                status = BusAttendanceStatusEnum.NORMAL.getValue();
            } else if (BusAttendanceClockStatusEnum.REISSUE.getValue().equals(clockInStatus)
                || BusAttendanceClockStatusEnum.REISSUE.getValue().equals(clockOutStatus)) {
                status = BusAttendanceStatusEnum.REISSUE.getValue();
            } else {
                status = BusAttendanceStatusEnum.ERROR.getValue();
            }
            resp.setStatus(status);
            resp.setAttendanceList(attendanceListByDayList);
            respList.add(resp);
        });
        // 按打卡日期正序排列
        respList.sort(Comparator.comparing(BusAttendanceMonthByUserIdVo::getClockDate));
        return respList;
    }

    /**
     * 查询符合条件的考勤列表
     *
     * @param req 查询条件
     * @return 考勤列表
     */
    @Override
    public List<BusAttendanceVo> queryList(BusAttendanceQueryReq req) {
        LambdaQueryWrapper<BusAttendance> lqw = buildQueryWrapper(req);
        return this.list(lqw).stream().map(this::getVo).toList();
    }

    /**
     * 根据项目id查询出勤人列表
     *
     * @param projectId 项目id
     * @return 出勤人列表
     */
    @Override
    public List<Long> listAttendancePeopleByProjectId(Long projectId) {
        // 今天所有用户的打卡记录
        List<BusAttendance> attendanceList = this.lambdaQuery()
            .eq(BusAttendance::getProjectId, projectId)
            .eq(BusAttendance::getClockDate, DateUtils.dateTimeNow(FormatsType.YYYY_MM_DD))
            .list();
        if (CollUtil.isEmpty(attendanceList)) {
            return List.of();
        }
        Map<Long, List<BusAttendance>> attendanceMap = attendanceList.stream()
            .collect(Collectors.groupingBy(BusAttendance::getUserId));
        List<Long> attendedUserIds = new ArrayList<>();
        for (Map.Entry<Long, List<BusAttendance>> entry : attendanceMap.entrySet()) {
            Long userId = entry.getKey();
            List<BusAttendance> records = entry.getValue();
            // 要求必须有2条打卡记录
            if (records.size() != 2) {
                continue;
            }
            boolean allValid = records.stream()
                .allMatch(record -> ATTENDANCE_STATUS.contains(record.getClockStatus()));
            if (allValid) {
                attendedUserIds.add(userId);
            }
        }
        return attendedUserIds;
    }

    /**
     * 获取考勤视图对象
     *
     * @param attendance 考勤对象
     * @return 考勤视图对象
     */
    @Override
    public BusAttendanceVo getVo(BusAttendance attendance) {
        // 对象转封装类
        BusAttendanceVo attendanceVo = new BusAttendanceVo();
        if (attendance == null) {
            return attendanceVo;
        }
        BeanUtils.copyProperties(attendance, attendanceVo);
        return attendanceVo;
    }

    /**
     * 获取考勤查询条件封装
     *
     * @param req 查询条件
     * @return 查询条件封装
     */
    @Override
    public LambdaQueryWrapper<BusAttendance> buildQueryWrapper(BusAttendanceQueryReq req) {
        LambdaQueryWrapper<BusAttendance> lqw = new LambdaQueryWrapper<>();
        if (req == null) {
            return lqw;
        }
        String userName = req.getUserName();
        Long projectId = req.getProjectId();
        Long teamId = req.getTeamId();
        String clockStatus = req.getClockStatus();
        Date clockDate = req.getClockDate();
        // 联表查询
        if (ObjectUtils.isNotEmpty(teamId)) {
            List<BusProjectTeamMember> projectTeamMemberList = projectTeamMemberService.lambdaQuery()
                .eq(BusProjectTeamMember::getTeamId, teamId).list();
            List<Long> userIdList = projectTeamMemberList.stream().map(BusProjectTeamMember::getMemberId).toList();
            lqw.in(BusAttendance::getUserId, userIdList);
        }
        // 模糊查询
        lqw.like(StringUtils.isNotBlank(userName), BusAttendance::getUserName, userName);
        // 精确查询
        lqw.eq(ObjectUtils.isNotEmpty(projectId), BusAttendance::getProjectId, projectId);
        lqw.eq(StringUtils.isNotBlank(clockStatus), BusAttendance::getClockStatus, clockStatus);
        lqw.eq(ObjectUtils.isNotEmpty(clockDate), BusAttendance::getClockDate, clockDate);
        // 不包含请假的考勤记录
        lqw.ne(BusAttendance::getClockStatus, BusAttendanceClockStatusEnum.LEAVE.getValue());
        return lqw;
    }

    /**
     * 获取考勤分页对象视图
     *
     * @param attendancePage 考勤分页对象
     * @return 考勤分页对象视图
     */
    @Override
    public Page<BusAttendanceVo> getVoPage(Page<BusAttendance> attendancePage) {
        List<BusAttendance> attendanceList = attendancePage.getRecords();
        Page<BusAttendanceVo> attendanceVoPage = new Page<>(
            attendancePage.getCurrent(),
            attendancePage.getSize(),
            attendancePage.getTotal()
        );
        if (CollUtil.isEmpty(attendanceList)) {
            return attendanceVoPage;
        }
        List<BusAttendanceVo> attendanceVoList = attendanceList.stream().map(this::getVo).toList();
        attendanceVoPage.setRecords(attendanceVoList);
        return attendanceVoPage;
    }

}
