package com.cf.imes.module.system.service.organ;

import com.cf.imes.framework.common.enums.CommonStatusEnum;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.organ.config.OrganProperties;
import com.cf.imes.framework.organ.core.context.OrganContextHolder;
import com.cf.imes.framework.test.core.ut.BaseDbUnitTest;
import com.cf.imes.module.system.controller.admin.organ.vo.organ.OrganPageReqVO;
import com.cf.imes.module.system.controller.admin.organ.vo.organ.OrganSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.permission.MenuDO;
import com.cf.imes.module.system.dal.dataobject.permission.RoleDO;
import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO;
import com.cf.imes.module.system.dal.dataobject.organ.TenantPackageDO;
import com.cf.imes.module.system.dal.mysql.organ.OrganMapper;
import com.cf.imes.module.system.enums.permission.RoleCodeEnum;
import com.cf.imes.module.system.enums.permission.RoleTypeEnum;
import com.cf.imes.module.system.service.permission.MenuService;
import com.cf.imes.module.system.service.permission.PermissionService;
import com.cf.imes.module.system.service.permission.RoleService;
import com.cf.imes.module.system.service.organ.handler.OrganInfoHandler;
import com.cf.imes.module.system.service.organ.handler.OrganMenuHandler;
import com.cf.imes.module.system.service.user.AdminUserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.cf.imes.framework.test.core.util.RandomUtils.*;
import static com.cf.imes.framework.common.util.collection.SetUtils.asSet;
import static com.cf.imes.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
import static com.cf.imes.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static com.cf.imes.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static com.cf.imes.framework.test.core.util.AssertUtils.assertPojoEquals;
import static com.cf.imes.framework.test.core.util.AssertUtils.assertServiceException;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.*;
import static java.util.Arrays.asList;
import static java.util.Collections.singleton;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * {@link OrganServiceImpl} 的单元测试类
 *
 * @author 晨丰科技
 */
@Import(OrganServiceImpl.class)
public class TenantServiceImplTest extends BaseDbUnitTest {

    @Resource
    private OrganServiceImpl tenantService;

    @Resource
    private OrganMapper tenantMapper;

    @MockBean
    private OrganProperties tenantProperties;
    @MockBean
    private TenantPackageService tenantPackageService;
    @MockBean
    private AdminUserService userService;
    @MockBean
    private RoleService roleService;
    @MockBean
    private MenuService menuService;
    @MockBean
    private PermissionService permissionService;

    @BeforeEach
    public void setUp() {
        // 清理组织上下文
        OrganContextHolder.clear();
    }

    @Test
    public void testgetOrganIdList() {
        // mock 数据
        OrganizationDO tenant = randomPojo(OrganizationDO.class, o -> o.setId(1L));
        tenantMapper.insert(tenant);

        // 调用，并断言业务异常
        List<Long> result = tenantService.getOrganIdList();
        assertEquals(Collections.singletonList(1L), result);
    }

    @Test
    public void testValidTenant_notExists() {
        assertServiceException(() -> tenantService.validOrgan(randomLongId()), ORGAN_NOT_EXISTS);
    }

    @Test
    public void testValidTenant_disable() {
        // mock 数据
        OrganizationDO tenant = randomPojo(OrganizationDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.DISABLE.getStatus()));
        tenantMapper.insert(tenant);

        // 调用，并断言业务异常
        assertServiceException(() -> tenantService.validOrgan(1L), ORGAN_DISABLE, tenant.getName());
    }

    @Test
    public void testValidTenant_expired() {
        // mock 数据
        OrganizationDO tenant = randomPojo(OrganizationDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.ENABLE.getStatus())
                .setExpireTime(buildTime(2020, 2, 2)));
        tenantMapper.insert(tenant);

        // 调用，并断言业务异常
        assertServiceException(() -> tenantService.validOrgan(1L), ORGAN_EXPIRE, tenant.getName());
    }

    @Test
    public void testValidTenant_success() {
        // mock 数据
        OrganizationDO tenant = randomPojo(OrganizationDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.ENABLE.getStatus())
                .setExpireTime(LocalDateTime.now().plusDays(1)));
        tenantMapper.insert(tenant);

        // 调用，并断言业务异常
        tenantService.validOrgan(1L);
    }

    @Test
    public void testCreateTenant() {
        // mock 套餐 100L
        TenantPackageDO tenantPackage = randomPojo(TenantPackageDO.class, o -> o.setId(100L));
        when(tenantPackageService.validTenantPackage(eq(100L))).thenReturn(tenantPackage);
        // mock 角色 200L
      /*  when(roleService.createRole(argThat(role -> {
            assertEquals(RoleCodeEnum.TENANT_ADMIN.getName(), role.getName());
            assertEquals(RoleCodeEnum.TENANT_ADMIN.getCode(), role.getCode());
            Assertions.assertEquals(0, role.getSort());
            Assertions.assertEquals("系统自动生成", role.getRemark());
            return true;
        }), eq(RoleTypeEnum.SYSTEM.getType()), null)).thenReturn(200L);
        // mock 用户 300L
        when(userService.createUser(argThat(user -> {
            assertEquals("yunai", user.getUsername());
            assertEquals("yuanma", user.getPassword());
            assertEquals("晨丰", user.getNickname());
            assertEquals("15601691300", user.getMobile());
            return true;
        }))).thenReturn(300L);
*/
        // 准备参数
        OrganSaveReqVO reqVO = randomPojo(OrganSaveReqVO.class, o -> {
            o.setContactName("晨丰");
            o.setContactMobile("15601691300");
            o.setPackageId(100L);
            o.setStatus(randomCommonStatus());
            o.setWebsite("https://www.cf.com");
            o.setUsername("yunai");
            o.setPassword("yuanma");
        }).setId(null); // 设置为 null，方便后面校验

        // 调用
        Long organId = tenantService.createOrgan(reqVO);
        // 断言
        assertNotNull(organId);
        // 校验记录的属性是否正确
        OrganizationDO tenant = tenantMapper.selectById(organId);
        assertPojoEquals(reqVO, tenant, "id");
        assertEquals(300L, tenant.getContactUserId());
        // verify 分配权限
        verify(permissionService).assignRoleMenu(eq(200L), same(tenantPackage.getMenuIds()), 1L);
        // verify 分配角色
        verify(permissionService).assignUserRole(eq(300L), eq(singleton(200L)),1L);
    }

    @Test
    public void testUpdateTenant_success() {
        // mock 数据
        OrganizationDO dbTenant = randomPojo(OrganizationDO.class, o -> o.setStatus(randomCommonStatus()));
        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
        // 准备参数
        OrganSaveReqVO reqVO = randomPojo(OrganSaveReqVO.class, o -> {
            o.setId(dbTenant.getId()); // 设置更新的 ID
            o.setStatus(randomCommonStatus());
            o.setWebsite(randomString());
        });

        // mock 套餐
        TenantPackageDO tenantPackage = randomPojo(TenantPackageDO.class,
                o -> o.setMenuIds(asSet(200L, 201L)));
        when(tenantPackageService.validTenantPackage(eq(reqVO.getPackageId()))).thenReturn(tenantPackage);
        // mock 所有角色
        RoleDO role100 = randomPojo(RoleDO.class, o -> o.setId(100L).setCode(RoleCodeEnum.TENANT_ADMIN.getCode()));
        role100.setOrganId(dbTenant.getId());
        RoleDO role101 = randomPojo(RoleDO.class, o -> o.setId(101L));
        role101.setOrganId(dbTenant.getId());
        when(roleService.getRoleList()).thenReturn(asList(role100, role101));
        // mock 每个角色的权限
        when(permissionService.getRoleMenuListByRoleId(eq(101L))).thenReturn(asSet(201L, 202L));

        // 调用
        tenantService.updateOrgan(reqVO);
        // 校验是否更新正确
        OrganizationDO tenant = tenantMapper.selectById(reqVO.getId()); // 获取最新的
        assertPojoEquals(reqVO, tenant);
        // verify 设置角色权限
        verify(permissionService).assignRoleMenu(eq(100L), eq(asSet(200L, 201L)), 1L);
        verify(permissionService).assignRoleMenu(eq(101L), eq(asSet(201L)), 1L);
    }

    @Test
    public void testUpdateTenant_notExists() {
        // 准备参数
        OrganSaveReqVO reqVO = randomPojo(OrganSaveReqVO.class);

        // 调用, 并断言异常
        assertServiceException(() -> tenantService.updateOrgan(reqVO), ORGAN_NOT_EXISTS);
    }

    @Test
    public void testUpdateTenant_system() {
        // mock 数据
        OrganizationDO dbTenant = randomPojo(OrganizationDO.class, o -> o.setPackageId(OrganizationDO.PACKAGE_ID_SYSTEM));
        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
        // 准备参数
        OrganSaveReqVO reqVO = randomPojo(OrganSaveReqVO.class, o -> {
            o.setId(dbTenant.getId()); // 设置更新的 ID
        });

        // 调用，校验业务异常
        assertServiceException(() -> tenantService.updateOrgan(reqVO), ORGAN_CAN_NOT_UPDATE_SYSTEM);
    }

    @Test
    public void testDeleteTenant_success() {
        // mock 数据
        OrganizationDO dbTenant = randomPojo(OrganizationDO.class,
                o -> o.setStatus(randomCommonStatus()));
        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
        // 准备参数
        Long id = dbTenant.getId();

        // 调用
        tenantService.deleteOrgan(id);
        // 校验数据不存在了
        assertNull(tenantMapper.selectById(id));
    }

    @Test
    public void testDeleteTenant_notExists() {
        // 准备参数
        Long id = randomLongId();

        // 调用, 并断言异常
        assertServiceException(() -> tenantService.deleteOrgan(id), ORGAN_NOT_EXISTS);
    }

    @Test
    public void testDeleteTenant_system() {
        // mock 数据
        OrganizationDO dbTenant = randomPojo(OrganizationDO.class, o -> o.setPackageId(OrganizationDO.PACKAGE_ID_SYSTEM));
        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
        // 准备参数
        Long id = dbTenant.getId();

        // 调用, 并断言异常
        assertServiceException(() -> tenantService.deleteOrgan(id), ORGAN_CAN_NOT_UPDATE_SYSTEM);
    }

    @Test
    public void testGetTenant() {
        // mock 数据
        OrganizationDO dbTenant = randomPojo(OrganizationDO.class);
        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
        // 准备参数
        Long id = dbTenant.getId();

        // 调用
        OrganizationDO result = tenantService.getOrgan(id);
        // 校验存在
        assertPojoEquals(result, dbTenant);
    }

    @Test
    public void testGetTenantPage() {
        // mock 数据
        OrganizationDO dbTenant = randomPojo(OrganizationDO.class, o -> { // 等会查询到
            o.setName("晨丰科技");
            o.setContactName("晨丰");
            o.setContactMobile("15601691300");
            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
            o.setCreateTime(buildTime(2020, 12, 12));
        });
        tenantMapper.insert(dbTenant);
        // 测试 name 不匹配
        tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setName(randomString())));
        // 测试 contactName 不匹配
        tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setContactName(randomString())));
        // 测试 contactMobile 不匹配
        tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setContactMobile(randomString())));
        // 测试 status 不匹配
        tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
        // 测试 createTime 不匹配
        tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setCreateTime(buildTime(2021, 12, 12))));
        // 准备参数
        OrganPageReqVO reqVO = new OrganPageReqVO();
        reqVO.setName("晨丰");
        reqVO.setContactName("艿");
        reqVO.setContactMobile("1560");
        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
        reqVO.setCreateTime(buildBetweenTime(2020, 12, 1, 2020, 12, 24));

        // 调用
        PageResult<OrganizationDO> pageResult = tenantService.getOrganPage(reqVO);
        // 断言
        assertEquals(1, pageResult.getTotal());
        assertEquals(1, pageResult.getList().size());
        assertPojoEquals(dbTenant, pageResult.getList().get(0));
    }

    @Test
    public void testGetTenantByName() {
        // mock 数据
        OrganizationDO dbTenant = randomPojo(OrganizationDO.class, o -> o.setName("晨丰"));
        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据

        // 调用
        OrganizationDO result = tenantService.getOrganByName("晨丰");
        // 校验存在
        assertPojoEquals(result, dbTenant);
    }

    @Test
    public void testGetTenantByWebsite() {
        // mock 数据
        OrganizationDO dbTenant = randomPojo(OrganizationDO.class, o -> o.setWebsite("https://www.cf.com"));
        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据

        // 调用
        OrganizationDO result = tenantService.getOrganByWebsite("https://www.cf.com");
        // 校验存在
        assertPojoEquals(result, dbTenant);
    }

    @Test
    public void testGetTenantListByPackageId() {
        // mock 数据
        OrganizationDO dbTenant1 = randomPojo(OrganizationDO.class, o -> o.setPackageId(1L));
        tenantMapper.insert(dbTenant1);// @Sql: 先插入出一条存在的数据
        OrganizationDO dbTenant2 = randomPojo(OrganizationDO.class, o -> o.setPackageId(2L));
        tenantMapper.insert(dbTenant2);// @Sql: 先插入出一条存在的数据

        // 调用
        List<OrganizationDO> result = tenantService.getOrganListByPackageId(1L);
        assertEquals(1, result.size());
        assertPojoEquals(dbTenant1, result.get(0));
    }

    @Test
    public void testGetTenantCountByPackageId() {
        // mock 数据
        OrganizationDO dbTenant1 = randomPojo(OrganizationDO.class, o -> o.setPackageId(1L));
        tenantMapper.insert(dbTenant1);// @Sql: 先插入出一条存在的数据
        OrganizationDO dbTenant2 = randomPojo(OrganizationDO.class, o -> o.setPackageId(2L));
        tenantMapper.insert(dbTenant2);// @Sql: 先插入出一条存在的数据

        // 调用
        Long count = tenantService.getOrganCountByPackageId(1L);
        assertEquals(1, count);
    }

    @Test
    public void testHandleTenantInfo_disable() {
        // 准备参数
        OrganInfoHandler handler = mock(OrganInfoHandler.class);
        // mock 禁用
        when(tenantProperties.getEnable()).thenReturn(false);

        // 调用
        tenantService.handleOrganInfo(handler);
        // 断言
        verify(handler, never()).handle(any());
    }

    @Test
    public void testHandleTenantInfo_success() {
        // 准备参数
        OrganInfoHandler handler = mock(OrganInfoHandler.class);
        // mock 未禁用
        when(tenantProperties.getEnable()).thenReturn(true);
        // mock 组织
        OrganizationDO dbTenant = randomPojo(OrganizationDO.class);
        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
        OrganContextHolder.setOrganId(dbTenant.getId());

        // 调用
        tenantService.handleOrganInfo(handler);
        // 断言
        verify(handler).handle(argThat(argument -> {
            assertPojoEquals(dbTenant, argument);
            return true;
        }));
    }

    @Test
    public void testHandleTenantMenu_disable() {
        // 准备参数
        OrganMenuHandler handler = mock(OrganMenuHandler.class);
        // mock 禁用
        when(tenantProperties.getEnable()).thenReturn(false);

        // 调用
        tenantService.handleOrganMenu(handler);
        // 断言
        verify(handler, never()).handle(any());
    }

    @Test // 系统组织的情况
    public void testHandleTenantMenu_system() {
        // 准备参数
        OrganMenuHandler handler = mock(OrganMenuHandler.class);
        // mock 未禁用
        when(tenantProperties.getEnable()).thenReturn(true);
        // mock 组织
        OrganizationDO dbTenant = randomPojo(OrganizationDO.class, o -> o.setPackageId(OrganizationDO.PACKAGE_ID_SYSTEM));
        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
        OrganContextHolder.setOrganId(dbTenant.getId());
        // mock 菜单
        when(menuService.getMenuList()).thenReturn(Arrays.asList(randomPojo(MenuDO.class, o -> o.setId(100L)),
                randomPojo(MenuDO.class, o -> o.setId(101L))));

        // 调用
        tenantService.handleOrganMenu(handler);
        // 断言
        verify(handler).handle(asSet(100L, 101L));
    }

    @Test // 普通组织的情况
    public void testHandleTenantMenu_normal() {
        // 准备参数
        OrganMenuHandler handler = mock(OrganMenuHandler.class);
        // mock 未禁用
        when(tenantProperties.getEnable()).thenReturn(true);
        // mock 组织
        OrganizationDO dbTenant = randomPojo(OrganizationDO.class, o -> o.setPackageId(200L));
        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据
        OrganContextHolder.setOrganId(dbTenant.getId());
        // mock 菜单
        when(tenantPackageService.getTenantPackage(eq(200L))).thenReturn(randomPojo(TenantPackageDO.class,
                o -> o.setMenuIds(asSet(100L, 101L))));

        // 调用
        tenantService.handleOrganMenu(handler);
        // 断言
        verify(handler).handle(asSet(100L, 101L));
    }
}
