package com.njcn.rdms.module.project.service.project;

import com.njcn.rdms.framework.common.exception.ServiceException;
import com.njcn.rdms.framework.test.core.ut.BaseMockitoUnitTest;
import com.njcn.rdms.module.project.constant.ProjectExecutionConstants;
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectRequirementDO;
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductRequirementMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductRequirementStatusLogMapper;
import com.njcn.rdms.module.project.dal.mysql.project.ProjectRequirementMapper;
import com.njcn.rdms.module.project.dal.mysql.project.ProjectRequirementModuleMapper;
import com.njcn.rdms.module.project.dal.mysql.project.ProjectRequirementStatusLogMapper;
import com.njcn.rdms.module.project.dal.mysql.project.execution.ProjectExecutionMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.project.framework.attachment.AttachmentFileIdResolver;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

/**
 * TD-013 引入的 {@link ProjectRequirementServiceImpl} 单测：
 * 仅覆盖 validateUsableForExecution 的 4 个分支与 deleteRequirement 挂载执行的新增前置校验。
 */
class ProjectRequirementServiceImplTest extends BaseMockitoUnitTest {

    @InjectMocks
    private ProjectRequirementServiceImpl projectRequirementService;
    @Mock
    private ProjectRequirementMapper requirementMapper;
    @Mock
    private ProjectRequirementModuleMapper moduleMapper;
    @Mock
    private ProjectRequirementStatusLogMapper statusLogMapper;
    @Mock
    private BizAuditLogMapper bizAuditLogMapper;
    @Mock
    private ProductRequirementMapper productRequirementMapper;
    @Mock
    private ProductRequirementStatusLogMapper productRequirementStatusLogMapper;
    @Mock
    private ObjectStatusTransitionMapper statusTransitionMapper;
    @Mock
    private ObjectStatusModelMapper statusModelMapper;
    @Mock
    private AttachmentFileIdResolver attachmentFileIdResolver;
    @Mock
    private ProjectExecutionMapper projectExecutionMapper;

    @Test
    void validateUsableForExecution_whenRequirementIdIsNull_shouldDoNothing() {
        assertDoesNotThrow(() -> projectRequirementService.validateUsableForExecution(2001L, null));
    }

    @Test
    void validateUsableForExecution_whenRequirementNotExists_shouldThrow() {
        when(requirementMapper.selectById(9001L)).thenReturn(null);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> projectRequirementService.validateUsableForExecution(2001L, 9001L));

        assertEquals(ErrorCodeConstants.PROJECT_EXECUTION_REQUIREMENT_NOT_EXISTS.getCode(), ex.getCode());
    }

    @Test
    void validateUsableForExecution_whenRequirementNotBelongToProject_shouldThrow() {
        when(requirementMapper.selectById(9001L)).thenReturn(buildRequirement(9001L, 2999L, "implementing"));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> projectRequirementService.validateUsableForExecution(2001L, 9001L));

        assertEquals(ErrorCodeConstants.PROJECT_EXECUTION_REQUIREMENT_NOT_BELONG_TO_PROJECT.getCode(), ex.getCode());
    }

    @Test
    void validateUsableForExecution_whenRequirementTerminal_shouldThrow() {
        when(requirementMapper.selectById(9001L)).thenReturn(buildRequirement(9001L, 2001L, "closed"));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> projectRequirementService.validateUsableForExecution(2001L, 9001L));

        assertEquals(ErrorCodeConstants.PROJECT_EXECUTION_REQUIREMENT_TERMINAL.getCode(), ex.getCode());
    }

    @Test
    void validateUsableForExecution_whenRequirementValid_shouldDoNothing() {
        when(requirementMapper.selectById(9001L)).thenReturn(buildRequirement(9001L, 2001L, "implementing"));

        assertDoesNotThrow(() -> projectRequirementService.validateUsableForExecution(2001L, 9001L));
    }

    /**
     * TD-013 Q4=A：项目需求下挂着承接执行时，禁止删除；前置校验必须先于 deleteByIdAndStatus 触发。
     */
    @Test
    void deleteRequirement_whenHasActiveExecutions_shouldThrow() {
        Long requirementId = 9001L;
        Long projectId = 2001L;
        when(requirementMapper.selectById(requirementId))
                .thenReturn(buildRequirement(requirementId, projectId, "pending_confirm"));
        when(requirementMapper.selectListByParentId(requirementId)).thenReturn(List.of());
        when(projectExecutionMapper.countActiveByProjectRequirementId(requirementId)).thenReturn(1L);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> projectRequirementService.deleteRequirement(requirementId, projectId));

        assertEquals(ErrorCodeConstants.PROJECT_REQUIREMENT_HAS_EXECUTIONS_NOT_ALLOW_DELETE.getCode(), ex.getCode());
    }

    private ProjectRequirementDO buildRequirement(Long id, Long projectId, String statusCode) {
        ProjectRequirementDO requirement = new ProjectRequirementDO();
        requirement.setId(id);
        requirement.setProjectId(projectId);
        requirement.setStatusCode(statusCode);
        return requirement;
    }

    // ============== TD-016 进度聚合 ==============

    /**
     * TD-016：叶子需求无承接执行 → 进度 0.00。
     */
    @Test
    void computeRequirementProgress_whenLeafHasNoExecution_shouldBeZero() {
        Long projectId = 2001L;
        ProjectRequirementDO leaf = buildRequirementWithParent(9001L, projectId, "implementing", 0L);
        when(requirementMapper.selectList(any(com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX.class)))
                .thenReturn(List.of(leaf));
        when(statusModelMapper.selectProgressExcludedStatusCodesByObjectTypeEnabled(ProjectExecutionConstants.OBJECT_TYPE))
                .thenReturn(List.of("cancelled"));
        when(projectExecutionMapper.selectAvgProgressGroupByProjectRequirementIds(any(), any()))
                .thenReturn(List.of());

        Map<Long, BigDecimal> result = projectRequirementService.computeRequirementProgressMapByProjectId(projectId);

        assertEquals(new BigDecimal("0.00"), result.get(9001L));
    }

    /**
     * TD-016：叶子需求有 N 个执行，AVG(progress_rate) 即结果。
     * （SQL 层已经 GROUP BY 平均；service 只是把那个平均值放回 pool 平均，pool 大小 1 等于自身）
     */
    @Test
    void computeRequirementProgress_whenLeafHasExecutions_shouldUseAvg() {
        Long projectId = 2001L;
        ProjectRequirementDO leaf = buildRequirementWithParent(9001L, projectId, "implementing", 0L);
        when(requirementMapper.selectList(any(com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX.class)))
                .thenReturn(List.of(leaf));
        when(statusModelMapper.selectProgressExcludedStatusCodesByObjectTypeEnabled(ProjectExecutionConstants.OBJECT_TYPE))
                .thenReturn(List.of("cancelled"));
        when(projectExecutionMapper.selectAvgProgressGroupByProjectRequirementIds(any(), any()))
                .thenReturn(List.of(rowOf(9001L, "0.60")));

        Map<Long, BigDecimal> result = projectRequirementService.computeRequirementProgressMapByProjectId(projectId);

        assertEquals(new BigDecimal("0.60"), result.get(9001L));
    }

    /**
     * TD-016：父需求 = AVG(自己挂的执行的平均, 每个直接子需求进度)。
     * 例：父 9000 自己挂的执行平均 0.40；子 9001（叶子）执行平均 0.80；
     * 父 = AVG(0.40, 0.80) = 0.60。子 9001 = 0.80。
     */
    @Test
    void computeRequirementProgress_whenParentHasOwnExecutionsAndChildren_shouldAveragePool() {
        Long projectId = 2001L;
        ProjectRequirementDO parent = buildRequirementWithParent(9000L, projectId, "implementing", 0L);
        ProjectRequirementDO child = buildRequirementWithParent(9001L, projectId, "implementing", 9000L);
        when(requirementMapper.selectList(any(com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX.class)))
                .thenReturn(List.of(parent, child));
        when(statusModelMapper.selectProgressExcludedStatusCodesByObjectTypeEnabled(ProjectExecutionConstants.OBJECT_TYPE))
                .thenReturn(List.of("cancelled"));
        when(projectExecutionMapper.selectAvgProgressGroupByProjectRequirementIds(any(), any()))
                .thenReturn(List.of(rowOf(9000L, "0.40"), rowOf(9001L, "0.80")));

        Map<Long, BigDecimal> result = projectRequirementService.computeRequirementProgressMapByProjectId(projectId);

        assertEquals(new BigDecimal("0.80"), result.get(9001L));
        assertEquals(new BigDecimal("0.60"), result.get(9000L));
    }

    /**
     * TD-016：父需求无自挂执行、有 2 个子需求 → 父 = AVG(子1, 子2)。
     */
    @Test
    void computeRequirementProgress_whenParentHasNoOwnExecutionPureChildren_shouldAverageChildren() {
        Long projectId = 2001L;
        ProjectRequirementDO parent = buildRequirementWithParent(9000L, projectId, "implementing", 0L);
        ProjectRequirementDO child1 = buildRequirementWithParent(9001L, projectId, "implementing", 9000L);
        ProjectRequirementDO child2 = buildRequirementWithParent(9002L, projectId, "implementing", 9000L);
        when(requirementMapper.selectList(any(com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX.class)))
                .thenReturn(List.of(parent, child1, child2));
        when(statusModelMapper.selectProgressExcludedStatusCodesByObjectTypeEnabled(ProjectExecutionConstants.OBJECT_TYPE))
                .thenReturn(List.of("cancelled"));
        when(projectExecutionMapper.selectAvgProgressGroupByProjectRequirementIds(any(), any()))
                .thenReturn(List.of(rowOf(9001L, "0.50"), rowOf(9002L, "1.00")));

        Map<Long, BigDecimal> result = projectRequirementService.computeRequirementProgressMapByProjectId(projectId);

        assertEquals(new BigDecimal("0.50"), result.get(9001L));
        assertEquals(new BigDecimal("1.00"), result.get(9002L));
        assertEquals(new BigDecimal("0.75"), result.get(9000L));
    }

    /**
     * TD-016：三层结构。爷父(9000) → 父(9100) → 叶子(9101)，每层都挂执行；
     * 验证自下而上递归正确。
     * 叶子 9101 自挂执行 0.80 → 9101 = 0.80
     * 父 9100 自挂执行 0.40 + 子 0.80 → AVG = 0.60
     * 爷 9000 自挂执行 0.20 + 子 0.60 → AVG = 0.40
     */
    @Test
    void computeRequirementProgress_multiLevel_shouldRecurseFromBottomUp() {
        Long projectId = 2001L;
        ProjectRequirementDO grand = buildRequirementWithParent(9000L, projectId, "implementing", 0L);
        ProjectRequirementDO parent = buildRequirementWithParent(9100L, projectId, "implementing", 9000L);
        ProjectRequirementDO leaf = buildRequirementWithParent(9101L, projectId, "implementing", 9100L);
        when(requirementMapper.selectList(any(com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX.class)))
                .thenReturn(List.of(grand, parent, leaf));
        when(statusModelMapper.selectProgressExcludedStatusCodesByObjectTypeEnabled(ProjectExecutionConstants.OBJECT_TYPE))
                .thenReturn(List.of("cancelled"));
        when(projectExecutionMapper.selectAvgProgressGroupByProjectRequirementIds(any(), any()))
                .thenReturn(List.of(rowOf(9000L, "0.20"), rowOf(9100L, "0.40"), rowOf(9101L, "0.80")));

        Map<Long, BigDecimal> result = projectRequirementService.computeRequirementProgressMapByProjectId(projectId);

        assertEquals(new BigDecimal("0.80"), result.get(9101L));
        assertEquals(new BigDecimal("0.60"), result.get(9100L));
        assertEquals(new BigDecimal("0.40"), result.get(9000L));
    }

    private ProjectRequirementDO buildRequirementWithParent(Long id, Long projectId, String statusCode, Long parentId) {
        ProjectRequirementDO requirement = buildRequirement(id, projectId, statusCode);
        requirement.setParentId(parentId);
        return requirement;
    }

    private Map<String, Object> rowOf(Long requirementId, String avg) {
        Map<String, Object> row = new HashMap<>();
        row.put("projectRequirementId", requirementId);
        row.put("progressRate", new BigDecimal(avg));
        return row;
    }
}
