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

import com.njcn.rdms.framework.common.exception.ServiceException;
import com.njcn.rdms.framework.test.core.ut.BaseMockitoUnitTest;
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberRespVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberUpdateReqVO;
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.product.ProductDO;
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.product.ProductMapper;
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 ProductMemberServiceImplTest extends BaseMockitoUnitTest {

    @InjectMocks
    private ProductMemberServiceImpl productMemberService;
    @Mock
    private ProductMapper productMapper;
    @Mock
    private UserObjectRoleMapper userObjectRoleMapper;
    @Mock
    private ObjectPermissionApi objectPermissionApi;
    @Mock
    private BizAuditLogMapper bizAuditLogMapper;
    @Mock
    private ObjectStatusModelMapper objectStatusModelMapper;
    @Mock
    private AdminUserApi adminUserApi;

    @Test
    void getProductMemberList_shouldFillRoleNameAndRoleCodeFromObjectPermissionApi() {
        Long productId = 1001L;
        ProductDO product = createProduct(productId, 2001L);
        UserObjectRoleDO manager = createMember(9001L, productId, 2001L, 3101L, 0);
        UserObjectRoleDO member = createMember(9002L, productId, 2002L, 3102L, 0);
        when(productMapper.selectById(productId)).thenReturn(product);
        when(userObjectRoleMapper.selectListByObject("product", productId)).thenReturn(List.of(manager, member));
        when(objectPermissionApi.getObjectRoleList(Set.of(3101L, 3102L), "object", "product"))
                .thenReturn(success(List.of(
                        createRole(3101L, "product_manager", "产品经理"),
                        createRole(3102L, "product_member", "产品成员")
                )));
        when(adminUserApi.getUserMap(Set.of(2001L, 2002L))).thenReturn(Map.of(
                2001L, createUser("经理甲"),
                2002L, createUser("成员乙")
        ));

        List<ProductMemberRespVO> respVOList = productMemberService.getProductMemberList(productId);

        assertEquals(2, respVOList.size());
        assertEquals("产品经理", respVOList.get(0).getRoleName());
        assertEquals("product_manager", respVOList.get(0).getRoleCode());
        assertEquals(Boolean.TRUE, respVOList.get(0).getManagerFlag());
        assertEquals("产品成员", respVOList.get(1).getRoleName());
        assertEquals("product_member", respVOList.get(1).getRoleCode());
        assertFalse(respVOList.get(1).getManagerFlag());
    }

    @Test
    void createProductMember_whenRoleSummaryMissing_shouldThrowRoleInvalid() {
        Long productId = 1002L;
        ProductMemberSaveReqVO reqVO = new ProductMemberSaveReqVO();
        reqVO.setUserId(2003L);
        reqVO.setRoleId(3999L);
        when(productMapper.selectById(productId)).thenReturn(createProduct(productId, 2001L));
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("product", "active"))
                .thenReturn(createStatus("active", true));
        when(objectPermissionApi.getObjectRoleById(3999L, "object", "product")).thenReturn(success(null));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> productMemberService.createProductMember(productId, reqVO));

        assertEquals(ErrorCodeConstants.PRODUCT_MEMBER_ROLE_INVALID.getCode(), ex.getCode());
        verify(userObjectRoleMapper, never()).selectByObjectUserAndRole(any(), any(), any(), any());
    }

    @Test
    void updateProductMember_whenPreviousManagerRoleCodeStillManager_shouldThrowException() {
        Long productId = 1003L;
        Long memberId = 9003L;
        Long currentManagerUserId = 2001L;
        Long targetManagerRoleId = 3201L;
        Long previousManagerRoleId = 3202L;
        ProductMemberUpdateReqVO reqVO = new ProductMemberUpdateReqVO();
        reqVO.setRoleId(targetManagerRoleId);
        reqVO.setPreviousManagerUserId(currentManagerUserId);
        reqVO.setPreviousManagerRoleId(previousManagerRoleId);
        reqVO.setReason("角色交接");

        ProductDO product = createProduct(productId, currentManagerUserId);
        UserObjectRoleDO member = createMember(memberId, productId, 2002L, 3203L, 0);
        when(productMapper.selectById(productId)).thenReturn(product);
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("product", "active"))
                .thenReturn(createStatus("active", true));
        when(userObjectRoleMapper.selectByIdAndObject(memberId, "product", productId)).thenReturn(member);
        when(objectPermissionApi.getObjectRoleById(targetManagerRoleId, "object", "product"))
                .thenReturn(success(createRole(targetManagerRoleId, "product_manager", "产品经理")));
        when(objectPermissionApi.getObjectRoleById(previousManagerRoleId, "object", "product"))
                .thenReturn(success(createRole(previousManagerRoleId, "product_manager", "产品经理")));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> productMemberService.updateProductMember(productId, memberId, reqVO));

        assertEquals(ErrorCodeConstants.PRODUCT_MANAGER_TRANSFER_ROLE_INVALID.getCode(), ex.getCode());
        verify(userObjectRoleMapper).updateById(member);
        verify(productMapper, never()).updateById(any(ProductDO.class));
        verify(bizAuditLogMapper, never()).insert(any(BizAuditLogDO.class));
    }

    @Test
    void createProductMember_whenStatusNotAllowEdit_shouldThrowException() {
        Long productId = 1004L;
        ProductMemberSaveReqVO reqVO = new ProductMemberSaveReqVO();
        reqVO.setUserId(2004L);
        reqVO.setRoleId(3204L);
        ProductDO product = createProduct(productId, 2001L);
        product.setStatusCode("archived");
        when(productMapper.selectById(productId)).thenReturn(product);
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("product", "archived"))
                .thenReturn(createStatus("archived", false));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> productMemberService.createProductMember(productId, reqVO));

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

    @Test
    void createProductMember_sameUserDifferentRole_shouldAllow() {
        Long productId = 1010L;
        ProductMemberSaveReqVO reqVO = new ProductMemberSaveReqVO();
        reqVO.setUserId(2002L);
        reqVO.setRoleId(3102L);
        when(productMapper.selectById(productId)).thenReturn(createProduct(productId, 2001L));
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("product", "active"))
                .thenReturn(createStatus("active", true));
        when(objectPermissionApi.getObjectRoleById(3102L, "object", "product"))
                .thenReturn(success(createRole(3102L, "product_specialist", "产品专员")));
        // user=2002L 在产品里 (2002L, 3102L) 这个 role 不存在 —— 即便 user 已有别的 role，新 role 应允许
        when(userObjectRoleMapper.selectByObjectUserAndRole("product", productId, 2002L, 3102L))
                .thenReturn(null);

        productMemberService.createProductMember(productId, reqVO);

        // 不抛 ALREADY_EXISTS，新加一行
        verify(userObjectRoleMapper).insert(any(UserObjectRoleDO.class));
        verify(userObjectRoleMapper, never()).updateById(any(UserObjectRoleDO.class));
    }

    @Test
    void createProductMember_inactiveSameRoleExists_shouldReactivate() {
        Long productId = 1011L;
        Long userId = 2002L;
        Long roleId = 3102L;
        ProductMemberSaveReqVO reqVO = new ProductMemberSaveReqVO();
        reqVO.setUserId(userId);
        reqVO.setRoleId(roleId);
        UserObjectRoleDO inactiveOld = createMember(9099L, productId, userId, roleId, 1);
        inactiveOld.setLeftTime(LocalDateTime.now().minusDays(7));
        when(productMapper.selectById(productId)).thenReturn(createProduct(productId, 2001L));
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("product", "active"))
                .thenReturn(createStatus("active", true));
        when(objectPermissionApi.getObjectRoleById(roleId, "object", "product"))
                .thenReturn(success(createRole(roleId, "product_specialist", "产品专员")));
        when(userObjectRoleMapper.selectByObjectUserAndRole("product", productId, userId, roleId))
                .thenReturn(inactiveOld);

        productMemberService.createProductMember(productId, reqVO);

        // 复活：updateById 被调，status=ACTIVE，leftTime=null；不调 insert
        ArgumentCaptor<UserObjectRoleDO> captor = ArgumentCaptor.forClass(UserObjectRoleDO.class);
        verify(userObjectRoleMapper).updateById(captor.capture());
        assertEquals(0, captor.getValue().getStatus());
        assertNull(captor.getValue().getLeftTime());
        verify(userObjectRoleMapper, never()).insert(any(UserObjectRoleDO.class));
    }

    private ProductDO createProduct(Long productId, Long managerUserId) {
        ProductDO product = new ProductDO();
        product.setId(productId);
        product.setManagerUserId(managerUserId);
        product.setStatusCode("active");
        product.setName("测试产品");
        product.setCode("CNPD2026001");
        return product;
    }

    private UserObjectRoleDO createMember(Long memberId, Long productId, Long userId, Long roleId, Integer status) {
        UserObjectRoleDO member = new UserObjectRoleDO();
        member.setId(memberId);
        member.setObjectType("product");
        member.setObjectId(productId);
        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("product");
        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("product");
        statusModel.setStatusCode(statusCode);
        statusModel.setAllowEdit(allowEdit);
        return statusModel;
    }

    // ============== review 遗留 4 条 issue 修复后的回归用例 ==============

    /** Issue 1：transferPreviousManager 时原经理在目标 role 上有 INACTIVE 历史行 → 抛 DUPLICATE 异常（不是撞 SQL）。 */
    @Test
    void transferPreviousManager_targetRoleHasInactiveHistory_shouldThrowDuplicate() {
        Long productId = 1020L;
        ProductMemberSaveReqVO reqVO = new ProductMemberSaveReqVO();
        reqVO.setUserId(2002L);
        reqVO.setRoleId(3201L);
        reqVO.setPreviousManagerUserId(2001L);
        reqVO.setPreviousManagerRoleId(3202L);
        ProductDO product = createProduct(productId, 2001L);
        UserObjectRoleDO inactiveOldRow = createMember(9077L, productId, 2001L, 3202L, 1);
        inactiveOldRow.setLeftTime(LocalDateTime.now().minusDays(30));
        when(productMapper.selectById(productId)).thenReturn(product);
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("product", "active"))
                .thenReturn(createStatus("active", true));
        when(objectPermissionApi.getObjectRoleById(3201L, "object", "product"))
                .thenReturn(success(createRole(3201L, "product_manager", "产品经理")));
        when(objectPermissionApi.getObjectRoleById(3202L, "object", "product"))
                .thenReturn(success(createRole(3202L, "product_specialist", "产品专员")));
        when(userObjectRoleMapper.selectByObjectUserAndRole("product", productId, 2002L, 3201L)).thenReturn(null);
        // 关键：原经理 (2001L, 3202L) 上有 INACTIVE 历史行 —— pre-check 用不带 status 的 select 命中
        when(userObjectRoleMapper.selectByObjectUserAndRole("product", productId, 2001L, 3202L))
                .thenReturn(inactiveOldRow);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> productMemberService.createProductMember(productId, reqVO));

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

    /** Issue 2：updateProductMember 改 roleId 时目标 role 已被该 user 的另一行占据 → 抛 ALREADY_EXISTS（不是撞 SQL）。 */
    @Test
    void updateProductMember_targetRoleHasOtherRow_shouldThrowAlreadyExists() {
        Long productId = 1021L;
        Long memberId = 9011L;
        ProductMemberUpdateReqVO reqVO = new ProductMemberUpdateReqVO();
        reqVO.setRoleId(3203L);
        UserObjectRoleDO member = createMember(memberId, productId, 2002L, 3202L, 0);
        // 该 user 已有另一行持有目标 role 3203L —— 直接 update 会撞唯一索引
        UserObjectRoleDO conflictRow = createMember(9012L, productId, 2002L, 3203L, 0);
        when(productMapper.selectById(productId)).thenReturn(createProduct(productId, 2001L));
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("product", "active"))
                .thenReturn(createStatus("active", true));
        when(userObjectRoleMapper.selectByIdAndObject(memberId, "product", productId)).thenReturn(member);
        when(objectPermissionApi.getObjectRoleById(3203L, "object", "product"))
                .thenReturn(success(createRole(3203L, "product_dev", "产品开发")));
        when(userObjectRoleMapper.selectByObjectUserAndRole("product", productId, 2002L, 3203L))
                .thenReturn(conflictRow);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> productMemberService.updateProductMember(productId, memberId, reqVO));

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

    /** Issue 3：复活分支审计动作应为 REACTIVATE，不是 ADD（语义区分新增 vs 软失效后再激活）。 */
    @Test
    void createProductMember_inactiveSameRoleExists_auditActionShouldBeReactivate() {
        Long productId = 1022L;
        Long userId = 2002L;
        Long roleId = 3202L;
        ProductMemberSaveReqVO reqVO = new ProductMemberSaveReqVO();
        reqVO.setUserId(userId);
        reqVO.setRoleId(roleId);
        UserObjectRoleDO inactiveOld = createMember(9088L, productId, userId, roleId, 1);
        inactiveOld.setLeftTime(LocalDateTime.now().minusDays(7));
        when(productMapper.selectById(productId)).thenReturn(createProduct(productId, 2001L));
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("product", "active"))
                .thenReturn(createStatus("active", true));
        when(objectPermissionApi.getObjectRoleById(roleId, "object", "product"))
                .thenReturn(success(createRole(roleId, "product_specialist", "产品专员")));
        when(userObjectRoleMapper.selectByObjectUserAndRole("product", productId, userId, roleId))
                .thenReturn(inactiveOld);

        productMemberService.createProductMember(productId, reqVO);

        ArgumentCaptor<BizAuditLogDO> auditCaptor = ArgumentCaptor.forClass(BizAuditLogDO.class);
        verify(bizAuditLogMapper).insert(auditCaptor.capture());
        // reactivate_member 来自 ObjectActivityConstants.MEMBER_ACTION_REACTIVATE，硬编码字面量避免引入额外 import
        assertEquals("reactivate_member", auditCaptor.getValue().getActionType());
    }

    /** Issue 4：内置 manager 角色未在 system_role 找到 → 抛业务 ServiceException（不是 IllegalStateException 透出 500）。 */
    @Test
    void resolveProductManagerRoleId_internalRoleMissing_shouldThrowBusinessException() {
        Long productId = 1023L;
        ProductMemberSaveReqVO reqVO = new ProductMemberSaveReqVO();
        reqVO.setUserId(2002L);
        reqVO.setRoleId(3201L);
        reqVO.setPreviousManagerUserId(2001L);
        reqVO.setPreviousManagerRoleId(3202L);
        ProductDO product = createProduct(productId, 2001L);
        when(productMapper.selectById(productId)).thenReturn(product);
        when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("product", "active"))
                .thenReturn(createStatus("active", true));
        when(objectPermissionApi.getObjectRoleById(3201L, "object", "product"))
                .thenReturn(success(createRole(3201L, "product_manager", "产品经理")));
        when(objectPermissionApi.getObjectRoleById(3202L, "object", "product"))
                .thenReturn(success(createRole(3202L, "product_specialist", "产品专员")));
        when(userObjectRoleMapper.selectByObjectUserAndRole("product", productId, 2002L, 3201L)).thenReturn(null);
        when(userObjectRoleMapper.selectByObjectUserAndRole("product", productId, 2001L, 3202L)).thenReturn(null);
        // 关键：内置 product_manager 角色查询返回 null —— resolveProductManagerRoleId 应抛业务异常
        when(objectPermissionApi.getObjectRoleByCode("product_manager", "object", "product"))
                .thenReturn(success(null));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> productMemberService.createProductMember(productId, reqVO));

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

}
