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

import com.njcn.rdms.framework.common.exception.ServiceException;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
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.ProjectDO;
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.ProjectMapper;
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 com.njcn.rdms.module.project.framework.notify.NotifySendEvent;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.springframework.context.ApplicationEventPublisher;

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.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
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;
    @Mock
    private com.njcn.rdms.module.project.service.status.StatusActionTextResolver statusActionTextResolver;
    @Mock
    private ProjectMapper projectMapper;
    @Mock
    private ApplicationEventPublisher applicationEventPublisher;

    @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());
    }

    /**
     * Task 8：创建项目需求后通知处理人（currentHandlerUserId），操作人由监听器统一排除。
     */
    @Test
    void createRequirement_notifiesHandler() {
        try (MockedStatic<SecurityFrameworkUtils> mocked = mockStatic(SecurityFrameworkUtils.class)) {
            mocked.when(SecurityFrameworkUtils::getLoginUserId).thenReturn(9L);
            ProjectRequirementDO req = new ProjectRequirementDO();
            req.setId(1L);
            req.setProjectId(10L);
            req.setTitle("登录改造");
            req.setCurrentHandlerUserId(5L);
            ProjectDO project = new ProjectDO();
            project.setProjectName("灿能项目");
            when(projectMapper.selectById(10L)).thenReturn(project);

            ArgumentCaptor<NotifySendEvent> captor = ArgumentCaptor.forClass(NotifySendEvent.class);
            projectRequirementService.publishRequirementAssignedNotify(req);
            verify(applicationEventPublisher).publishEvent(captor.capture());
            NotifySendEvent ev = captor.getValue();
            assertEquals("project_requirement_assigned", ev.getTemplateCode());
            assertEquals(9L, ev.getOperatorUserId());
            assertTrue(ev.getUserIds().contains(5L));
            assertEquals("登录改造", ev.getParams().get("requirementTitle"));
            assertEquals("灿能项目", ev.getParams().get("projectName"));
        }
    }

    /**
     * Task 8：处理人为空时不发通知。
     */
    @Test
    void createRequirement_whenHandlerNull_shouldNotPublish() {
        ProjectRequirementDO req = new ProjectRequirementDO();
        req.setId(1L);
        req.setProjectId(10L);
        req.setTitle("登录改造");
        req.setCurrentHandlerUserId(null);

        projectRequirementService.publishRequirementAssignedNotify(req);
        verify(applicationEventPublisher, never()).publishEvent(any());
    }

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

}
