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

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.dal.dataobject.member.UserObjectRoleDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
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.enums.ErrorCodeConstants;
import com.njcn.rdms.module.project.service.datascope.ObjectDataScope;
import com.njcn.rdms.module.project.service.datascope.ObjectDataScopeService;
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;

import java.util.Collections;
import java.util.List;
import java.util.Set;

import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

class ProductObjectPermissionServiceTest extends BaseMockitoUnitTest {

    @InjectMocks
    private ProductObjectPermissionService permissionService;
    @Mock
    private UserObjectRoleMapper userObjectRoleMapper;
    @Mock
    private ObjectPermissionApi objectPermissionApi;
    @Mock
    private ProductMapper productMapper;
    @Mock
    private ObjectDataScopeService objectDataScopeService;

    @Test
    void checkPermission_whenMemberOnlyAndCurrentUserIsMember_shouldPass() {
        Long productId = 1001L;
        Long loginUserId = 2001L;
        when(userObjectRoleMapper.selectActiveListByObjectAndUserId("product", productId, loginUserId))
                .thenReturn(List.of(createMember(productId, loginUserId, 3001L)));

        try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId)) {
            assertDoesNotThrow(() -> permissionService.checkPermission(productId, null, true));
        }

        verifyNoInteractions(objectPermissionApi);
    }

    @Test
    void checkPermission_whenCurrentRolePermissionsContainTarget_shouldPass() {
        Long productId = 1002L;
        Long loginUserId = 2002L;
        when(userObjectRoleMapper.selectActiveListByObjectAndUserId("product", productId, loginUserId))
                .thenReturn(List.of(createMember(productId, loginUserId, 3002L)));
        when(objectPermissionApi.getObjectRolePermissions(3002L,
                PermissionScopeTypeEnum.OBJECT.getScopeType(), "product"))
                .thenReturn(success(Set.of("project:product:query")));

        try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId)) {
            assertDoesNotThrow(() -> permissionService.checkPermission(productId, "project:product:query", false));
        }
    }

    @Test
    void checkPermission_whenCurrentRoleDoesNotContainPermission_shouldThrowException() {
        Long productId = 1003L;
        Long loginUserId = 2003L;
        when(userObjectRoleMapper.selectActiveListByObjectAndUserId("product", productId, loginUserId))
                .thenReturn(List.of(createMember(productId, loginUserId, 3003L)));
        when(objectPermissionApi.getObjectRolePermissions(3003L,
                PermissionScopeTypeEnum.OBJECT.getScopeType(), "product"))
                .thenReturn(success(Set.of("project:product:update")));

        try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId)) {
            ServiceException ex = assertThrows(ServiceException.class,
                    () -> permissionService.checkPermission(productId, "project:product:delete", false));
            assertEquals(ErrorCodeConstants.PRODUCT_OBJECT_PERMISSION_DENIED.getCode(), ex.getCode());
            // 用户可见 message 不得外泄权限码（技术细节走 log.warn）
            assertFalse(ex.getMessage().contains(":"), "message 不应外泄权限码");
        }
    }

    @Test
    void checkPermission_whenCurrentUserIsNotMember_shouldThrowException() {
        Long productId = 1004L;
        Long loginUserId = 2004L;
        when(userObjectRoleMapper.selectActiveListByObjectAndUserId("product", productId, loginUserId))
                .thenReturn(Collections.emptyList());

        try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId)) {
            ServiceException ex = assertThrows(ServiceException.class,
                    () -> permissionService.checkPermission(productId, "project:product:query", false));
            assertEquals(ErrorCodeConstants.PRODUCT_OBJECT_PERMISSION_DENIED.getCode(), ex.getCode());
            assertFalse(ex.getMessage().contains(":"), "message 不应外泄权限码");
        }

        verifyNoInteractions(objectPermissionApi);
    }

    @Test
    void checkPermission_whenMemberOnlyAndNotMember_shouldThrowWithoutLeak() {
        Long productId = 1005L;
        Long loginUserId = 2005L;
        when(userObjectRoleMapper.selectActiveListByObjectAndUserId("product", productId, loginUserId))
                .thenReturn(Collections.emptyList());

        try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId)) {
            ServiceException ex = assertThrows(ServiceException.class,
                    () -> permissionService.checkPermission(productId, "project:product:query", true));
            assertEquals(ErrorCodeConstants.PRODUCT_OBJECT_PERMISSION_DENIED.getCode(), ex.getCode());
            // memberOnly 分支原先会外泄字面 member 标记，现统一为友好文案
            assertFalse(ex.getMessage().contains("member"), "message 不应外泄 member 标记");
            assertFalse(ex.getMessage().contains(":"), "message 不应外泄权限码");
        }
    }

    @Test
    void checkAccessible_whenActiveMember_shouldPass() {
        Long productId = 1101L;
        Long loginUserId = 2101L;
        when(userObjectRoleMapper.selectActiveListByObjectAndUserId("product", productId, loginUserId))
                .thenReturn(List.of(createMember(productId, loginUserId, 3101L)));

        try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId)) {
            assertDoesNotThrow(() -> permissionService.checkAccessible(productId));
        }
        verifyNoInteractions(productMapper, objectDataScopeService);
    }

    @Test
    void checkAccessible_whenImplicitObserverInScope_shouldPass() {
        Long productId = 1102L;
        Long loginUserId = 2102L;
        when(userObjectRoleMapper.selectActiveListByObjectAndUserId("product", productId, loginUserId))
                .thenReturn(Collections.emptyList());
        ProductDO product = new ProductDO();
        product.setId(productId);
        product.setDirectionCode("D-NET");
        when(productMapper.selectById(productId)).thenReturn(product);
        when(objectDataScopeService.compute(loginUserId, "product"))
                .thenReturn(ObjectDataScope.idList(Collections.emptySet(), Set.of("D-NET")));

        try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId)) {
            assertDoesNotThrow(() -> permissionService.checkAccessible(productId));
        }
    }

    @Test
    void checkAccessible_whenSuperAdminScopeAll_shouldPass() {
        Long productId = 1103L;
        Long loginUserId = 2103L;
        when(userObjectRoleMapper.selectActiveListByObjectAndUserId("product", productId, loginUserId))
                .thenReturn(Collections.emptyList());
        ProductDO product = new ProductDO();
        product.setId(productId);
        product.setDirectionCode("D-NET");
        when(productMapper.selectById(productId)).thenReturn(product);
        when(objectDataScopeService.compute(loginUserId, "product"))
                .thenReturn(ObjectDataScope.all());

        try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId)) {
            assertDoesNotThrow(() -> permissionService.checkAccessible(productId));
        }
    }

    @Test
    void checkAccessible_whenNeitherMemberNorScope_shouldThrowDenied() {
        Long productId = 1104L;
        Long loginUserId = 2104L;
        when(userObjectRoleMapper.selectActiveListByObjectAndUserId("product", productId, loginUserId))
                .thenReturn(Collections.emptyList());
        ProductDO product = new ProductDO();
        product.setId(productId);
        product.setDirectionCode("D-NET");
        when(productMapper.selectById(productId)).thenReturn(product);
        when(objectDataScopeService.compute(loginUserId, "product"))
                .thenReturn(ObjectDataScope.empty());

        try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId)) {
            ServiceException ex = assertThrows(ServiceException.class,
                    () -> permissionService.checkAccessible(productId));
            assertEquals(ErrorCodeConstants.PRODUCT_OBJECT_PERMISSION_DENIED.getCode(), ex.getCode());
            assertFalse(ex.getMessage().contains(":"), "message 不应外泄权限码");
        }
    }

    @Test
    void checkAccessible_whenObjectNotExists_shouldThrowDenied() {
        Long productId = 1105L;
        Long loginUserId = 2105L;
        when(userObjectRoleMapper.selectActiveListByObjectAndUserId("product", productId, loginUserId))
                .thenReturn(Collections.emptyList());
        when(productMapper.selectById(productId)).thenReturn(null);

        try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId)) {
            ServiceException ex = assertThrows(ServiceException.class,
                    () -> permissionService.checkAccessible(productId));
            assertEquals(ErrorCodeConstants.PRODUCT_OBJECT_PERMISSION_DENIED.getCode(), ex.getCode());
        }
        verifyNoInteractions(objectDataScopeService);
    }

    private UserObjectRoleDO createMember(Long productId, Long loginUserId, Long roleId) {
        UserObjectRoleDO member = new UserObjectRoleDO();
        member.setId(9001L);
        member.setUserId(loginUserId);
        member.setObjectType("product");
        member.setObjectId(productId);
        member.setRoleId(roleId);
        member.setStatus(0);
        return member;
    }

    private MockedStatic<SecurityFrameworkUtils> mockLoginUser(Long loginUserId) {
        MockedStatic<SecurityFrameworkUtils> mockedStatic = mockStatic(SecurityFrameworkUtils.class);
        mockedStatic.when(SecurityFrameworkUtils::getLoginUserId).thenReturn(loginUserId);
        return mockedStatic;
    }

}
