package com.njcn.rdms.module.project.dal.mysql.personal;

import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.rdms.module.project.controller.admin.personal.vo.item.PersonalItemPageReqVO;
import com.njcn.rdms.module.project.dal.dataobject.personal.PersonalItemDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.util.StringUtils;

import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import java.util.Map;

@Mapper
public interface PersonalItemMapper extends BaseMapperX<PersonalItemDO> {

    default PersonalItemDO selectByIdAndOwnerId(Long id, Long ownerId) {
        return selectOne(new LambdaQueryWrapperX<PersonalItemDO>()
                .eq(PersonalItemDO::getId, id)
                .eq(PersonalItemDO::getOwnerId, ownerId));
    }

    default PageResult<PersonalItemDO> selectPageByOwnerId(Long ownerId, PersonalItemPageReqVO reqVO) {
        LambdaQueryWrapperX<PersonalItemDO> queryWrapper = new LambdaQueryWrapperX<>();
        queryWrapper.eq(PersonalItemDO::getOwnerId, ownerId);
        queryWrapper.eqIfPresent(PersonalItemDO::getStatusCode, reqVO.getStatusCode());
        queryWrapper.betweenIfPresent(BaseDO::getUpdateTime, reqVO.getUpdateTime());
        queryWrapper.orderByDesc(BaseDO::getUpdateTime);
        queryWrapper.orderByDesc(PersonalItemDO::getId);
        if (StringUtils.hasText(reqVO.getKeyword())) {
            queryWrapper.and(wrapper -> wrapper.like(PersonalItemDO::getTaskTitle, reqVO.getKeyword()));
        }
        return selectPage(reqVO, queryWrapper);
    }

    default int updateStatusByIdAndStatus(Long id, String fromStatus, String toStatus, String lastStatusReason) {
        PersonalItemDO update = new PersonalItemDO();
        update.setStatusCode(toStatus);
        update.setLastStatusReason(lastStatusReason);
        return update(update, new LambdaQueryWrapperX<PersonalItemDO>()
                .eq(PersonalItemDO::getId, id)
                .eq(PersonalItemDO::getStatusCode, fromStatus));
    }

    default int deleteByIdAndStatus(Long id, String fromStatus) {
        return delete(new LambdaQueryWrapperX<PersonalItemDO>()
                .eq(PersonalItemDO::getId, id)
                .eq(PersonalItemDO::getStatusCode, fromStatus));
    }

    default List<PersonalItemDO> selectPlannedListByOwnerIdAndOverlap(Long ownerId, LocalDate startDate, LocalDate endDate,
                                                                      String excludedStatusCode) {
        LambdaQueryWrapperX<PersonalItemDO> queryWrapper = new LambdaQueryWrapperX<PersonalItemDO>()
                .eq(PersonalItemDO::getOwnerId, ownerId);
        if (StringUtils.hasText(excludedStatusCode)) {
            queryWrapper.ne(PersonalItemDO::getStatusCode, excludedStatusCode);
        }
        queryWrapper.isNotNull(PersonalItemDO::getPlannedStartDate);
        queryWrapper.isNotNull(PersonalItemDO::getPlannedEndDate);
        queryWrapper.le(PersonalItemDO::getPlannedStartDate, endDate);
        queryWrapper.ge(PersonalItemDO::getPlannedEndDate, startDate);
        queryWrapper.orderByAsc(PersonalItemDO::getPlannedStartDate);
        queryWrapper.orderByAsc(PersonalItemDO::getId);
        return selectList(queryWrapper);
    }

    default List<PersonalItemDO> selectListByOwnerIdAndStatusNot(Long ownerId, String excludedStatusCode) {
        if (ownerId == null) {
            return List.of();
        }
        LambdaQueryWrapperX<PersonalItemDO> queryWrapper = new LambdaQueryWrapperX<PersonalItemDO>()
                .eq(PersonalItemDO::getOwnerId, ownerId);
        if (StringUtils.hasText(excludedStatusCode)) {
            queryWrapper.ne(PersonalItemDO::getStatusCode, excludedStatusCode);
        }
        queryWrapper.orderByAsc(PersonalItemDO::getId);
        return selectList(queryWrapper);
    }

    /**
     * 团队负载:一批成员的非终态个人事项计数(按 owner_id 分组),带临期/逾期。
     * 个人事项状态机复用 task 对象类型,terminalStatusCodes 与任务同源。
     * 返回 Map:ownerId / itemCount / dueSoonCount / overdueCount(均 Long)。
     */
    @Select("""
            <script>
            SELECT owner_id AS ownerId,
                   CAST(COUNT(*) AS SIGNED) AS itemCount,
                   CAST(SUM(CASE WHEN planned_end_date IS NOT NULL
                        AND planned_end_date &gt;= #{today}
                        AND planned_end_date &lt;= #{dueSoonEnd}
                        THEN 1 ELSE 0 END) AS SIGNED) AS dueSoonCount,
                   CAST(SUM(CASE WHEN planned_end_date IS NOT NULL
                        AND planned_end_date &lt; #{today}
                        THEN 1 ELSE 0 END) AS SIGNED) AS overdueCount
            FROM rdms_personal_item
            WHERE deleted = b'0'
              AND owner_id IN
              <foreach collection="ownerIds" item="uid" open="(" separator="," close=")">#{uid}</foreach>
              <if test="terminalStatusCodes != null and !terminalStatusCodes.isEmpty()">
                  AND status_code NOT IN
                  <foreach collection="terminalStatusCodes" item="tc" open="(" separator="," close=")">#{tc}</foreach>
              </if>
            GROUP BY owner_id
            </script>
            """)
    List<Map<String, Object>> selectLoadStatsByOwnerIds(
            @Param("ownerIds") Collection<Long> ownerIds,
            @Param("terminalStatusCodes") Collection<String> terminalStatusCodes,
            @Param("today") LocalDate today,
            @Param("dueSoonEnd") LocalDate dueSoonEnd);

    /**
     * 临期/逾期告警候选：计划完成日非空且 <= maxPlannedEndDate，状态不在排除集（终态+paused）。
     * excludeStatusCodes 为空时不排除任何状态（空集守卫，与既有口径一致）。
     * 设计见 docs/domains/project/2026-06-12-临期逾期告警-设计.html。
     */
    default List<PersonalItemDO> selectDueAlertCandidateList(LocalDate maxPlannedEndDate,
                                                             Collection<String> excludeStatusCodes) {
        return selectList(new LambdaQueryWrapperX<PersonalItemDO>()
                .isNotNull(PersonalItemDO::getPlannedEndDate)
                .le(PersonalItemDO::getPlannedEndDate, maxPlannedEndDate)
                .notIn(excludeStatusCodes != null && !excludeStatusCodes.isEmpty(),
                        PersonalItemDO::getStatusCode, excludeStatusCodes));
    }
}
