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

import com.njcn.rdms.framework.common.exception.ServiceException;
import com.njcn.rdms.module.project.controller.admin.project.vo.member.ProjectMemberInactiveReqVO;
import com.njcn.rdms.framework.test.core.ut.BaseMockitoUnitTest;
import com.njcn.rdms.module.project.controller.admin.project.vo.member.ProjectMemberRespVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.member.ProjectMemberSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.member.ProjectMemberUpdateReqVO;
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO;
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
import com.njcn.rdms.module.project.dal.mysql.project.ProjectMapper;
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.enums.ErrorCodeConstants;
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
import com.njcn.rdms.module.system.api.permission.dto.ObjectRoleRespDTO;
import com.njcn.rdms.module.system.api.user.AdminUserApi;
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class ProjectMemberServiceImplTest extends BaseMockitoUnitTest {

    @InjectMocks
    private ProjectMemberServiceImpl projectMemberService;
    @Mock
    private ProjectMapper projectMapper;
    @Mock
    private UserObjectRoleMapper userObjectRoleMapper;
    @Mock
    private ObjectPermissionApi objectPermissionApi;
    @Mock
    private BizAuditLogMapper bizAuditLogMapper;
    @Mock
    private ObjectStatusModelMapper objectStatusModelMapper;
    @Mock
    private AdminUserApi adminUserApi;
    @Mock
    private ProjectExecutionMapper projectExecutionMapper;

    @Test
    void getProjectMemberList_shouldFillRoleAndUserInfo() {
        Long projectId = 1001L;
        ProjectDO project = createProject(projectId, 2001L);
        UserObjectRoleDO manager = createMember(9001L, projectId, 2001L, 3101L, 0);
        UserObjectRoleDO member = createMember(9002L, projectId, 2002L, 3102L, 0);
        when(projectMapper.selectById(projectId)).thenReturn(project);
        when(userObjectRoleMapper.selectListByObject("project", projectId)).thenReturn(List.of(manager, member));
        when(objectPermissionApi.getObjectRoleList(Set.of(3101L, 3102L), "object", "project"))
                .thenReturn(success(List.of(
                        createRole(3101L, "project_manager", "项目经理"),
                        createRole(3102L, "project_member_configured", "项目成员")
                )));
        when(adminUserApi.getUserMap(Set.of(2001L, 2002L))).thenReturn(Map.of(
                2001L, createUser("经理甲"),
                2002L, createUser("成员乙")
        ));

        List<ProjectMemberRespVO> respVOList = projectMemberService.getProjectMemberList(projectId);

        assertEquals(2, respVOList.size());
        assertEquals("项目经理", respVOList.get(0).getRoleName());
        assertEquals("project_manager", respVOList.get(0).getRoleCode());
        assertEquals(Boolean.TRUE, respVOList.get(0).getManagerFlag());
        assertEquals("项目成员", respVOList.get(1).getRoleName());
        assertFalse(respVOList.get(1).getManagerFlag());
    }

    @Test
    void createProjectMember_whenUserAlreadyActive_shouldThrowAlreadyExists() {
        Long projectId = 1002L;
        ProjectMemberSaveReqVO reqVO = new ProjectMemberSaveReqVO();
        reqVO.setUserId(2002L);
        reqVO.setRoleId(3102L);
        when(projectMapper.selectById(projectId)).thenReturn(createProject(projectId, 2001L));
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "active"))
                .thenReturn(createStatus("active", true));
        when(adminUserApi.validateUserList(any())).thenReturn(success(true));
        when(objectPermissionApi.getObjectRoleById(3102L, "object", "project"))
                .thenReturn(success(createRole(3102L, "project_member_configured", "项目成员")));
        when(userObjectRoleMapper.selectByObjectAndUserId("project", projectId, 2002L))
                .thenReturn(createMember(9002L, projectId, 2002L, 3102L, 0));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> projectMemberService.createProjectMember(projectId, reqVO));

        assertEquals(ErrorCodeConstants.PROJECT_MEMBER_ALREADY_EXISTS.getCode(), ex.getCode());
        verify(userObjectRoleMapper, never()).insert(any(UserObjectRoleDO.class));
        verify(bizAuditLogMapper, never()).insert(any(BizAuditLogDO.class));
    }

    @Test
    void createProjectMember_whenRoleInvalid_shouldThrowRoleInvalid() {
        Long projectId = 1003L;
        ProjectMemberSaveReqVO reqVO = new ProjectMemberSaveReqVO();
        reqVO.setUserId(2003L);
        reqVO.setRoleId(3999L);
        when(projectMapper.selectById(projectId)).thenReturn(createProject(projectId, 2001L));
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "active"))
                .thenReturn(createStatus("active", true));
        when(adminUserApi.validateUserList(any())).thenReturn(success(true));
        when(objectPermissionApi.getObjectRoleById(3999L, "object", "project")).thenReturn(success(null));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> projectMemberService.createProjectMember(projectId, reqVO));

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

    @Test
    void createProjectMember_whenManagerTransfer_shouldKeepPreviousManagerActiveWithNewRole() {
        Long projectId = 1004L;
        ProjectMemberSaveReqVO reqVO = new ProjectMemberSaveReqVO();
        reqVO.setUserId(2002L);
        reqVO.setRoleId(3101L);
        reqVO.setPreviousManagerUserId(2001L);
        reqVO.setPreviousManagerRoleId(3102L);
        ProjectDO project = createProject(projectId, 2001L);
        UserObjectRoleDO previousManager = createMember(9001L, projectId, 2001L, 3101L, 0);
        previousManager.setLeftTime(LocalDateTime.now());
        when(projectMapper.selectById(projectId)).thenReturn(project);
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "active"))
                .thenReturn(createStatus("active", true));
        when(adminUserApi.validateUserList(any())).thenReturn(success(true));
        when(objectPermissionApi.getObjectRoleById(3101L, "object", "project"))
                .thenReturn(success(createRole(3101L, "project_manager", "项目经理")));
        when(objectPermissionApi.getObjectRoleById(3102L, "object", "project"))
                .thenReturn(success(createRole(3102L, "project_member_configured", "项目成员")));
        when(userObjectRoleMapper.selectByObjectAndUserId("project", projectId, 2002L)).thenReturn(null);
        when(userObjectRoleMapper.selectByObjectAndUserId("project", projectId, 2001L)).thenReturn(previousManager);

        projectMemberService.createProjectMember(projectId, reqVO);

        ArgumentCaptor<UserObjectRoleDO> previousManagerCaptor = ArgumentCaptor.forClass(UserObjectRoleDO.class);
        verify(userObjectRoleMapper).updateById(previousManagerCaptor.capture());
        assertEquals(2001L, previousManagerCaptor.getValue().getUserId());
        assertEquals(3102L, previousManagerCaptor.getValue().getRoleId());
        assertEquals(0, previousManagerCaptor.getValue().getStatus());
        assertNull(previousManagerCaptor.getValue().getLeftTime());
        assertEquals(2002L, project.getManagerUserId());
        verify(projectMapper).updateById(project);
    }

    @Test
    void updateProjectMember_whenManagerTransferInfoMissing_shouldThrowInfoRequired() {
        Long projectId = 1005L;
        Long memberId = 9002L;
        ProjectMemberUpdateReqVO reqVO = new ProjectMemberUpdateReqVO();
        reqVO.setRoleId(3101L);
        UserObjectRoleDO member = createMember(memberId, projectId, 2002L, 3102L, 0);
        when(projectMapper.selectById(projectId)).thenReturn(createProject(projectId, 2001L));
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "active"))
                .thenReturn(createStatus("active", true));
        when(userObjectRoleMapper.selectByIdAndObject(memberId, "project", projectId)).thenReturn(member);
        when(objectPermissionApi.getObjectRoleById(3101L, "object", "project"))
                .thenReturn(success(createRole(3101L, "project_manager", "项目经理")));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> projectMemberService.updateProjectMember(projectId, memberId, reqVO));

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

    @Test
    void updateProjectMember_whenCurrentManagerDowngrade_shouldThrowNotAllowDowngrade() {
        Long projectId = 1006L;
        Long memberId = 9001L;
        ProjectMemberUpdateReqVO reqVO = new ProjectMemberUpdateReqVO();
        reqVO.setRoleId(3102L);
        UserObjectRoleDO member = createMember(memberId, projectId, 2001L, 3101L, 0);
        when(projectMapper.selectById(projectId)).thenReturn(createProject(projectId, 2001L));
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "active"))
                .thenReturn(createStatus("active", true));
        when(userObjectRoleMapper.selectByIdAndObject(memberId, "project", projectId)).thenReturn(member);
        when(objectPermissionApi.getObjectRoleById(3102L, "object", "project"))
                .thenReturn(success(createRole(3102L, "project_member_configured", "项目成员")));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> projectMemberService.updateProjectMember(projectId, memberId, reqVO));

        assertEquals(ErrorCodeConstants.PROJECT_MANAGER_MEMBER_NOT_ALLOW_DOWNGRADE.getCode(), ex.getCode());
        verify(userObjectRoleMapper, never()).updateById(any(UserObjectRoleDO.class));
    }

    @Test
    void inactiveProjectMember_whenStillOwnsOpenExecution_shouldThrowHandoffRequired() {
        Long projectId = 1001L;
        Long memberId = 9002L;
        UserObjectRoleDO member = createMember(memberId, projectId, 2002L, 3102L, 0);
        when(projectMapper.selectById(projectId)).thenReturn(createProject(projectId, 2001L));
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "active"))
                .thenReturn(createStatus("active", true));
        when(userObjectRoleMapper.selectByIdAndObject(memberId, "project", projectId)).thenReturn(member);
        when(objectStatusModelMapper.selectListByObjectType("execution"))
                .thenReturn(List.of(createExecutionStatus("completed", true), createExecutionStatus("cancelled", true)));
        when(projectExecutionMapper.countNonTerminalByProjectIdAndOwnerId(projectId, 2002L,
                List.of("completed", "cancelled"))).thenReturn(1);

        ProjectMemberInactiveReqVO reqVO = new ProjectMemberInactiveReqVO();
        reqVO.setReason("退出项目");

        ServiceException ex = assertThrows(ServiceException.class,
                () -> projectMemberService.inactiveProjectMember(projectId, memberId, reqVO));

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

    private ProjectDO createProject(Long projectId, Long managerUserId) {
        ProjectDO project = new ProjectDO();
        project.setId(projectId);
        project.setManagerUserId(managerUserId);
        project.setStatusCode("active");
        project.setProjectName("测试项目");
        project.setProjectCode("CNPJ2026001");
        return project;
    }

    private UserObjectRoleDO createMember(Long memberId, Long projectId, Long userId, Long roleId, Integer status) {
        UserObjectRoleDO member = new UserObjectRoleDO();
        member.setId(memberId);
        member.setObjectType("project");
        member.setObjectId(projectId);
        member.setUserId(userId);
        member.setRoleId(roleId);
        member.setStatus(status);
        member.setJoinedTime(LocalDateTime.now());
        return member;
    }

    private ObjectRoleRespDTO createRole(Long roleId, String roleCode, String roleName) {
        ObjectRoleRespDTO role = new ObjectRoleRespDTO();
        role.setId(roleId);
        role.setCode(roleCode);
        role.setName(roleName);
        role.setScopeType("object");
        role.setObjectType("project");
        return role;
    }

    private AdminUserRespDTO createUser(String nickname) {
        AdminUserRespDTO user = new AdminUserRespDTO();
        user.setNickname(nickname);
        return user;
    }

    private ObjectStatusModelDO createStatus(String statusCode, boolean allowEdit) {
        ObjectStatusModelDO statusModel = new ObjectStatusModelDO();
        statusModel.setObjectType("project");
        statusModel.setStatusCode(statusCode);
        statusModel.setAllowEdit(allowEdit);
        return statusModel;
    }

    private ObjectStatusModelDO createExecutionStatus(String statusCode, boolean terminalFlag) {
        ObjectStatusModelDO statusModel = new ObjectStatusModelDO();
        statusModel.setObjectType("execution");
        statusModel.setStatusCode(statusCode);
        statusModel.setTerminalFlag(terminalFlag);
        return statusModel;
    }

}
