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

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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.project.execution.vo.execution.ProjectExecutionStatusBoardReqVO;
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution.ProjectExecutionPageReqVO;
import com.njcn.rdms.module.project.dal.dataobject.project.execution.ProjectExecutionDO;
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 ProjectExecutionMapper extends BaseMapperX<ProjectExecutionDO> {

    default ProjectExecutionDO selectByProjectIdAndId(Long projectId, Long executionId) {
        return selectOne(new LambdaQueryWrapperX<ProjectExecutionDO>()
                .eq(ProjectExecutionDO::getProjectId, projectId)
                .eq(ProjectExecutionDO::getId, executionId));
    }

    default ProjectExecutionDO selectByProjectIdAndName(Long projectId, String executionName) {
        return selectOne(new LambdaQueryWrapperX<ProjectExecutionDO>()
                .eq(ProjectExecutionDO::getProjectId, projectId)
                .eq(ProjectExecutionDO::getExecutionName, executionName));
    }

    default PageResult<ProjectExecutionDO> selectPageByProjectId(Long projectId,
                                                                 ProjectExecutionPageReqVO reqVO,
                                                                 List<String> terminalStatusCodes,
                                                                 LocalDate today,
                                                                 LocalDate weekStart,
                                                                 LocalDate weekEnd) {
        Page<ProjectExecutionDO> page = new Page<>(reqVO.getPageNo(), reqVO.getPageSize());
        IPage<ProjectExecutionDO> ipage = doSelectPageByProjectId(
                projectId, reqVO, terminalStatusCodes, today, weekStart, weekEnd, page);
        return new PageResult<>(ipage.getRecords(), ipage.getTotal());
    }

    /**
     * 项目下执行分页查询。
     * <p>SQL 用 @Select 直接控制，主表以别名 t 暴露，EXISTS 子查询用 t.id 关联；
     * 避免 LambdaWrapper + .apply 嵌入裸 SQL 时依赖 "MyBatis-Plus 不给主表加别名" 这一实现细节。
     * 与任务侧 {@code ProjectTaskMapper.selectAggregatePageByProjectId} 同款风格。
     *
     * <p>involveUserId 非空时附加 (owner_id = involveUserId OR 活跃协办含 involveUserId)；
     * 否则该过滤分支跳过（"看项目下全部"）。
     */
    @Select("""
            <script>
            SELECT t.*
            FROM rdms_project_execution t
            <where>
                t.deleted = b'0'
                AND t.project_id = #{projectId}
                <if test="reqVO.executionType != null and reqVO.executionType != ''">
                    AND t.execution_type = #{reqVO.executionType}
                </if>
                <if test="reqVO.ownerId != null">
                    AND t.owner_id = #{reqVO.ownerId}
                </if>
                <if test="reqVO.statusCode != null and reqVO.statusCode != ''">
                    AND t.status_code = #{reqVO.statusCode}
                </if>
                <if test="reqVO.priority != null and reqVO.priority != ''">
                    AND t.priority = #{reqVO.priority}
                </if>
                <if test="reqVO.updateTime != null and reqVO.updateTime.length == 2">
                    AND t.update_time BETWEEN #{reqVO.updateTime[0]} AND #{reqVO.updateTime[1]}
                </if>
                <if test="reqVO.keyword != null and reqVO.keyword != ''">
                    AND t.execution_name LIKE CONCAT('%', #{reqVO.keyword}, '%')
                </if>
                <if test="reqVO.involveUserId != null">
                    AND (
                        t.owner_id = #{reqVO.involveUserId}
                        OR EXISTS (
                            SELECT 1 FROM rdms_execution_assignee a
                            WHERE a.execution_id = t.id
                              AND a.user_id = #{reqVO.involveUserId}
                              AND a.removed_at IS NULL
                              AND a.deleted = b'0'
                        )
                    )
                </if>
                <!-- 截止时间范围 chip：基于 planned_end_date，三个桶均排除终态（对齐任务 summary 口径） -->
                <if test="reqVO.dueRange == 'overdue'">
                    AND t.planned_end_date &lt; #{today}
                    <if test="terminalStatusCodes != null and !terminalStatusCodes.isEmpty()">
                        AND t.status_code NOT IN
                        <foreach collection="terminalStatusCodes" item="tc" open="(" separator="," close=")">#{tc}</foreach>
                    </if>
                </if>
                <if test="reqVO.dueRange == 'today'">
                    AND t.planned_end_date = #{today}
                    <if test="terminalStatusCodes != null and !terminalStatusCodes.isEmpty()">
                        AND t.status_code NOT IN
                        <foreach collection="terminalStatusCodes" item="tc" open="(" separator="," close=")">#{tc}</foreach>
                    </if>
                </if>
                <if test="reqVO.dueRange == 'thisWeek'">
                    AND t.planned_end_date BETWEEN #{weekStart} AND #{weekEnd}
                    <if test="terminalStatusCodes != null and !terminalStatusCodes.isEmpty()">
                        AND t.status_code NOT IN
                        <foreach collection="terminalStatusCodes" item="tc" open="(" separator="," close=")">#{tc}</foreach>
                    </if>
                </if>
            </where>
            ORDER BY t.priority ASC, t.create_time DESC, t.id DESC
            </script>
            """)
    IPage<ProjectExecutionDO> doSelectPageByProjectId(
            @Param("projectId") Long projectId,
            @Param("reqVO") ProjectExecutionPageReqVO reqVO,
            @Param("terminalStatusCodes") List<String> terminalStatusCodes,
            @Param("today") LocalDate today,
            @Param("weekStart") LocalDate weekStart,
            @Param("weekEnd") LocalDate weekEnd,
            Page<ProjectExecutionDO> page);

    default List<ProjectExecutionDO> selectListByOwnerId(Long ownerId) {
        return selectList(new LambdaQueryWrapperX<ProjectExecutionDO>()
                .eq(ProjectExecutionDO::getOwnerId, ownerId)
                .orderByDesc(BaseDO::getCreateTime)
                .orderByDesc(ProjectExecutionDO::getId));
    }

    default Integer countNonTerminalByProjectIdAndOwnerId(Long projectId, Long ownerId, List<String> terminalStatusCodes) {
        LambdaQueryWrapperX<ProjectExecutionDO> queryWrapper = new LambdaQueryWrapperX<ProjectExecutionDO>()
                .eq(ProjectExecutionDO::getProjectId, projectId)
                .eq(ProjectExecutionDO::getOwnerId, ownerId);
        if (terminalStatusCodes != null && !terminalStatusCodes.isEmpty()) {
            queryWrapper.notIn(ProjectExecutionDO::getStatusCode, terminalStatusCodes);
        }
        return Math.toIntExact(selectCount(queryWrapper));
    }

    default Integer countByProjectIdAndStatusCode(Long projectId,
                                                  ProjectExecutionStatusBoardReqVO reqVO,
                                                  String statusCode,
                                                  List<String> terminalStatusCodes,
                                                  LocalDate today,
                                                  LocalDate weekStart,
                                                  LocalDate weekEnd) {
        return Math.toIntExact(doCountByProjectIdAndStatusCode(
                projectId, reqVO, statusCode, terminalStatusCodes, today, weekStart, weekEnd));
    }

    /**
     * 统计指定项目下处于非终态的执行数。用于项目 complete 前置校验（TD-015）。
     */
    default Integer countNonTerminalByProjectId(Long projectId, List<String> terminalStatusCodes) {
        LambdaQueryWrapperX<ProjectExecutionDO> queryWrapper = new LambdaQueryWrapperX<ProjectExecutionDO>()
                .eq(ProjectExecutionDO::getProjectId, projectId);
        if (terminalStatusCodes != null && !terminalStatusCodes.isEmpty()) {
            queryWrapper.notIn(ProjectExecutionDO::getStatusCode, terminalStatusCodes);
        }
        return Math.toIntExact(selectCount(queryWrapper));
    }

    /**
     * 项目下指定状态的执行计数（与 doSelectPageByProjectId 同款过滤口径）。
     * 同上：用 @Select 显式表别名 t 替代 LambdaWrapper + .apply EXISTS 写法。
     */
    @Select("""
            <script>
            SELECT COUNT(*)
            FROM rdms_project_execution t
            <where>
                t.deleted = b'0'
                AND t.project_id = #{projectId}
                AND t.status_code = #{statusCode}
                <if test="reqVO.executionType != null and reqVO.executionType != ''">
                    AND t.execution_type = #{reqVO.executionType}
                </if>
                <if test="reqVO.ownerId != null">
                    AND t.owner_id = #{reqVO.ownerId}
                </if>
                <if test="reqVO.updateTime != null and reqVO.updateTime.length == 2">
                    AND t.update_time BETWEEN #{reqVO.updateTime[0]} AND #{reqVO.updateTime[1]}
                </if>
                <if test="reqVO.keyword != null and reqVO.keyword != ''">
                    AND t.execution_name LIKE CONCAT('%', #{reqVO.keyword}, '%')
                </if>
                <if test="reqVO.involveUserId != null">
                    AND (
                        t.owner_id = #{reqVO.involveUserId}
                        OR EXISTS (
                            SELECT 1 FROM rdms_execution_assignee a
                            WHERE a.execution_id = t.id
                              AND a.user_id = #{reqVO.involveUserId}
                              AND a.removed_at IS NULL
                              AND a.deleted = b'0'
                        )
                    )
                </if>
                <!-- 截止时间范围 chip：基于 planned_end_date，三个桶均排除终态（对齐任务 summary 口径） -->
                <if test="reqVO.dueRange == 'overdue'">
                    AND t.planned_end_date &lt; #{today}
                    <if test="terminalStatusCodes != null and !terminalStatusCodes.isEmpty()">
                        AND t.status_code NOT IN
                        <foreach collection="terminalStatusCodes" item="tc" open="(" separator="," close=")">#{tc}</foreach>
                    </if>
                </if>
                <if test="reqVO.dueRange == 'today'">
                    AND t.planned_end_date = #{today}
                    <if test="terminalStatusCodes != null and !terminalStatusCodes.isEmpty()">
                        AND t.status_code NOT IN
                        <foreach collection="terminalStatusCodes" item="tc" open="(" separator="," close=")">#{tc}</foreach>
                    </if>
                </if>
                <if test="reqVO.dueRange == 'thisWeek'">
                    AND t.planned_end_date BETWEEN #{weekStart} AND #{weekEnd}
                    <if test="terminalStatusCodes != null and !terminalStatusCodes.isEmpty()">
                        AND t.status_code NOT IN
                        <foreach collection="terminalStatusCodes" item="tc" open="(" separator="," close=")">#{tc}</foreach>
                    </if>
                </if>
            </where>
            </script>
            """)
    Long doCountByProjectIdAndStatusCode(
            @Param("projectId") Long projectId,
            @Param("reqVO") ProjectExecutionStatusBoardReqVO reqVO,
            @Param("statusCode") String statusCode,
            @Param("terminalStatusCodes") List<String> terminalStatusCodes,
            @Param("today") LocalDate today,
            @Param("weekStart") LocalDate weekStart,
            @Param("weekEnd") LocalDate weekEnd);

    /**
     * TD-016：按 projectRequirementIds 批量聚合承接执行的平均进度，避免列表 N+1。
     * <p>口径：仅统计 deleted = 0 + status_code NOT IN(excludedStatusCodes) 的执行；
     * excludedStatusCodes 由 rdms_object_status_model.progress_excluded_flag=1 动态读取，不在代码硬编码。
     * 返回行格式：{ projectRequirementId, progressRate }（仅出现的 requirementId 才有行，无承接执行的不返回）。
     */
    @Select("""
            <script>
            SELECT project_requirement_id AS projectRequirementId,
                   AVG(COALESCE(progress_rate, 0)) AS progressRate
            FROM rdms_project_execution
            WHERE deleted = b'0'
              AND project_requirement_id IN
                <foreach collection="projectRequirementIds" item="id" open="(" separator="," close=")">#{id}</foreach>
              <if test="excludedStatusCodes != null and excludedStatusCodes.size() > 0">
              AND status_code NOT IN
                <foreach collection="excludedStatusCodes" item="statusCode" open="(" separator="," close=")">#{statusCode}</foreach>
              </if>
            GROUP BY project_requirement_id
            </script>
            """)
    List<Map<String, Object>> selectAvgProgressGroupByProjectRequirementIds(
            @Param("projectRequirementIds") Collection<Long> projectRequirementIds,
            @Param("excludedStatusCodes") Collection<String> excludedStatusCodes);

    /**
     * 统计指定项目需求下的有效（未软删）承接执行数。
     * 用于项目需求删除前的"先解绑/转移承接执行"前置校验。
     */
    default long countActiveByProjectRequirementId(Long projectRequirementId) {
        return selectCount(new LambdaQueryWrapperX<ProjectExecutionDO>()
                .eq(ProjectExecutionDO::getProjectRequirementId, projectRequirementId));
    }

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

    /**
     * 软删执行（按状态 CAS）。仅在 statusCode 与传入 fromStatus 匹配时才标 deleted=1。
     * 返回 1 表示成功；返回 0 视为并发修改。
     */
    default int deleteByIdAndStatus(Long id, String fromStatus) {
        return delete(new LambdaQueryWrapperX<ProjectExecutionDO>()
                .eq(ProjectExecutionDO::getId, id)
                .eq(ProjectExecutionDO::getStatusCode, fromStatus));
    }

    /**
     * 接口二：一批项目下的进行中执行数（按 project_id 分组，排除终态）。
     * 返回 Map：projectId(Long) / executionCount(Long)。
     */
    @Select("""
            <script>
            SELECT project_id AS projectId,
                   CAST(COUNT(*) AS SIGNED) AS executionCount
            FROM rdms_project_execution
            WHERE deleted = b'0'
              AND project_id IN
              <foreach collection="projectIds" item="pid" open="(" separator="," close=")">#{pid}</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 project_id
            </script>
            """)
    List<Map<String, Object>> selectExecutionCountGroupByProjectIds(
            @Param("projectIds") Collection<Long> projectIds,
            @Param("terminalStatusCodes") Collection<String> terminalStatusCodes);

    default List<ProjectExecutionDO> selectListByProjectId(Long projectId) {
        if (projectId == null) {
            return java.util.Collections.emptyList();
        }
        return selectList(new LambdaQueryWrapperX<ProjectExecutionDO>()
                .eq(ProjectExecutionDO::getProjectId, projectId)
                .orderByAsc(ProjectExecutionDO::getId));
    }

    default List<ProjectExecutionDO> selectPlannedListByProjectIdAndOverlap(Long projectId, LocalDate startDate,
                                                                            LocalDate endDate, String excludedStatusCode) {
        if (projectId == null || startDate == null || endDate == null) {
            return java.util.Collections.emptyList();
        }
        LambdaQueryWrapperX<ProjectExecutionDO> queryWrapper = new LambdaQueryWrapperX<ProjectExecutionDO>()
                .eq(ProjectExecutionDO::getProjectId, projectId);
        if (StringUtils.hasText(excludedStatusCode)) {
            queryWrapper.ne(ProjectExecutionDO::getStatusCode, excludedStatusCode);
        }
        queryWrapper.isNotNull(ProjectExecutionDO::getPlannedStartDate);
        queryWrapper.isNotNull(ProjectExecutionDO::getPlannedEndDate);
        queryWrapper.le(ProjectExecutionDO::getPlannedStartDate, endDate);
        queryWrapper.ge(ProjectExecutionDO::getPlannedEndDate, startDate);
        queryWrapper.orderByAsc(ProjectExecutionDO::getPlannedStartDate);
        queryWrapper.orderByAsc(ProjectExecutionDO::getId);
        return selectList(queryWrapper);
    }

    default List<ProjectExecutionDO> selectListByProjectIdAndStatusNot(Long projectId, String excludedStatusCode) {
        if (projectId == null) {
            return java.util.Collections.emptyList();
        }
        LambdaQueryWrapperX<ProjectExecutionDO> queryWrapper = new LambdaQueryWrapperX<ProjectExecutionDO>()
                .eq(ProjectExecutionDO::getProjectId, projectId);
        if (StringUtils.hasText(excludedStatusCode)) {
            queryWrapper.ne(ProjectExecutionDO::getStatusCode, excludedStatusCode);
        }
        queryWrapper.orderByAsc(ProjectExecutionDO::getId);
        return selectList(queryWrapper);
    }

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

}
