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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.cf.imes.framework.common.enums.CommonStatusEnum;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.collection.CollectionUtils;
import com.cf.imes.framework.common.util.date.DateUtils;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.framework.common.util.pinyin.PinYinUtils;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.organ.config.OrganProperties;
import com.cf.imes.framework.organ.core.context.OrganContextHolder;
import com.cf.imes.framework.organ.core.util.OrganUtils;
import com.cf.imes.module.system.controller.admin.organ.vo.organ.OrganSimpleRespVO;
import com.cf.imes.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;
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.controller.admin.user.vo.user.UserSaveReqVO;
import com.cf.imes.module.system.controller.admin.user.vo.user.UserSimpleRespVO;
import com.cf.imes.module.system.convert.organ.OrganConvert;
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 com.baomidou.dynamic.datasource.annotation.DSTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.*;
import static java.util.Collections.singleton;

/**
 * 组织 Service 实现类
 *
 * @author 晨丰科技
 */
@Service
@Validated
@Slf4j
public class OrganServiceImpl implements OrganService {

    @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
    @Autowired(required = false) // 由于 chenfeng.tenant.enable 配置项，可以关闭多组织的功能，所以这里只能不强制注入
    private OrganProperties organProperties;

    @Resource
    private OrganMapper organMapper;

    @Resource
    private TenantPackageService tenantPackageService;
    @Resource
    @Lazy // 延迟，避免循环依赖报错
    private AdminUserService userService;
    @Resource
    private RoleService roleService;
    @Resource
    private MenuService menuService;
    @Resource
    private PermissionService permissionService;
    //组织管理员角色id
    public static final Long ORGAN_ADMIN_ROLE_ID = 165L;
    //组织员工角色id
    public static final Long ORGAN_STAFF_ROLE_ID = 166L;

    @Value("${chenfeng.organ.use-data-code:imes_prod}")
    private String useDataCode;

    @Override
    public List<Long> getOrganIdList() {
        List<OrganizationDO> organizationDOS = organMapper.selectList();
        return CollectionUtils.convertList(organizationDOS, OrganizationDO::getId);
    }

    @Override
    public void validOrgan(Long id) {
        OrganizationDO organizationDO = getOrgan(id);
        if (organizationDO == null) {
            throw exception(ORGAN_NOT_EXISTS);
        }
        if (organizationDO.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) {
            throw exception(ORGAN_DISABLE, organizationDO.getName());
        }
        if (DateUtils.isExpired(organizationDO.getExpireTime())) {
            throw exception(ORGAN_EXPIRE, organizationDO.getName());
        }
    }

    @Override
    public List<OrganSimpleRespVO> getSimpleOrganList(String name) {
        List<OrganizationDO> organizationDOS = organMapper.selectList(new LambdaQueryWrapperX<OrganizationDO>()
                .likeIfPresent(OrganizationDO::getName, name)
                .select(OrganizationDO::getId, OrganizationDO::getName));
       return BeanUtils.toBean(organizationDOS, OrganSimpleRespVO.class);
    }

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    public Long createOrgan(OrganSaveReqVO createReqVO) {
        // 校验组织名称是否重复
        validTenantNameDuplicate(createReqVO.getName(), null);
        // 校验组织域名是否重复
        validTenantWebsiteDuplicate(createReqVO.getWebsite(), null);
        // 校验套餐被禁用
        TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(createReqVO.getPackageId());

        // 创建组织
        OrganizationDO tenant = BeanUtils.toBean(createReqVO, OrganizationDO.class);
        String pinyinFull = PinYinUtils.convertToPinyin(tenant.getName());
        if(StrUtil.isNotBlank(pinyinFull)) {
            pinyinFull = pinyinFull.replaceAll(" ", "");
        }
        tenant.setPinyinFull(pinyinFull);
        tenant.setCode(StrUtil.isBlank(createReqVO.getCode())? pinyinFull : createReqVO.getCode());
        tenant.setLarge(createReqVO.getLarge() == null ? Boolean.FALSE : createReqVO.getLarge());
        tenant.setPinyinInitial(PinYinUtils.convertFirstChar(tenant.getName()));
        //todo 未来还需要判断是否大客户，大客户有自己dataCode
        tenant.setDataSourceCode(useDataCode);

        organMapper.insert(tenant);
        Long organId = tenant.getId();
        if (!tenant.getLarge()) {
            tenant.setDbNo(organId.intValue() % 8);
        }
        tenant.setTableNo(organId.intValue() % 256);
        organMapper.updateById(tenant);
        // 创建组织的管理员
        OrganUtils.execute(organId, () -> {
            // 创建角色
            /*Long roleId = createRole(tenantPackage, organId);*/
            // 创建用户，并分配角色
            Long userId = createUser(ORGAN_ADMIN_ROLE_ID, createReqVO, organId);
            // 修改组织的管理员
            organMapper.updateById(new OrganizationDO().setId(tenant.getId()).setContactUserId(userId));
        });
        return tenant.getId();
    }

    private Long createUser(Long roleId, OrganSaveReqVO createReqVO, Long organId) {
        // 创建用户
        UserSaveReqVO reqVO = OrganConvert.INSTANCE.convert02(createReqVO);
        reqVO.setOrganId(organId);
        Long userId = userService.createUser(reqVO);
        // 分配角色
        permissionService.assignUserRole(userId, singleton(roleId), organId);
        return userId;
    }

    private Long createRole(TenantPackageDO tenantPackage, Long organId) {
        // 创建角色
        RoleSaveReqVO reqVO = new RoleSaveReqVO();
        reqVO.setName(RoleCodeEnum.TENANT_ADMIN.getName()).setCode(RoleCodeEnum.TENANT_ADMIN.getCode())
                .setSort(0).setRemark("系统自动生成").setOrganId(organId);
        Long roleId = roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType());
        // 分配权限
        permissionService.assignRoleMenu(roleId, tenantPackage.getMenuIds(), organId);
        return roleId;
    }

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    public void updateOrgan(OrganSaveReqVO updateReqVO) {
        // 校验存在
        OrganizationDO tenant = validateUpdateTenant(updateReqVO.getId());
        // 校验组织名称是否重复
        validTenantNameDuplicate(updateReqVO.getName(), updateReqVO.getId());
        // 校验组织域名是否重复
        validTenantWebsiteDuplicate(updateReqVO.getWebsite(), updateReqVO.getId());
        // 校验套餐被禁用
        TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(updateReqVO.getPackageId());
        if(!Objects.equals(tenant.getName(), updateReqVO.getName())) {
            String pinyinFull = PinYinUtils.convertToPinyin(tenant.getName());
            if(StrUtil.isNotBlank(pinyinFull)) {
                pinyinFull = pinyinFull.replaceAll(" ", "");
            }
            tenant.setPinyinFull(pinyinFull);
            tenant.setPinyinInitial(PinYinUtils.convertFirstChar(tenant.getName()));
        }
        // 更新组织
        OrganizationDO updateObj = BeanUtils.toBean(updateReqVO, OrganizationDO.class);
        organMapper.updateById(updateObj);
        // 如果套餐发生变化，则修改其角色的权限
        if (ObjectUtil.notEqual(tenant.getPackageId(), updateReqVO.getPackageId())) {
            updateOrganRoleMenu(tenant.getId(), tenantPackage.getMenuIds());
        }
    }

    private void validTenantNameDuplicate(String name, Long id) {
        OrganizationDO tenant = organMapper.selectByName(name);
        if (tenant == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同名字的组织
        if (id == null) {
            throw exception(ORGAN_NAME_DUPLICATE, name);
        }
        if (!tenant.getId().equals(id)) {
            throw exception(ORGAN_NAME_DUPLICATE, name);
        }
    }

    private void validTenantWebsiteDuplicate(String website, Long id) {
        if (StrUtil.isEmpty(website)) {
            return;
        }
        OrganizationDO tenant = organMapper.selectByWebsite(website);
        if (tenant == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同名字的组织
        if (id == null) {
            throw exception(ORGAN_WEBSITE_DUPLICATE, website);
        }
        if (!tenant.getId().equals(id)) {
            throw exception(ORGAN_WEBSITE_DUPLICATE, website);
        }
    }

    @Override
    @DSTransactional
    public void updateOrganRoleMenu(Long organId, Set<Long> menuIds) {
        OrganUtils.execute(organId, () -> {
            // 获得所有角色
            List<RoleDO> roles = roleService.getMeAndDefaultRoleList();
            roles.forEach(role -> Assert.isTrue(organId.equals(role.getOrganId()) || role.getOrganId().equals(0L), "角色({}/{}) 组织不匹配",
                    role.getId(), role.getOrganId(), organId)); // 兜底校验
            // 重新分配每个角色的权限
            roles.forEach(role -> {
                // 如果是组织管理员，重新分配其权限为组织套餐的权限
                if (Objects.equals(role.getCode(), RoleCodeEnum.TENANT_ADMIN.getCode())) {
                    permissionService.assignRoleMenu(role.getId(), menuIds, organId);
                    log.info("[updateTenantRoleMenu][组织管理员({}/{}) 的权限修改为({})]", role.getId(), role.getOrganId(), menuIds);
                    return;
                }
                // 如果是其他角色，则去掉超过套餐的权限
                Set<Long> roleMenuIds = permissionService.getRoleMenuListByRoleId(role.getId());
                roleMenuIds = CollUtil.intersectionDistinct(roleMenuIds, menuIds);
                permissionService.assignRoleMenu(role.getId(), roleMenuIds, organId);
                log.info("[updateTenantRoleMenu][角色({}/{}) 的权限修改为({})]", role.getId(), role.getOrganId(), roleMenuIds);
            });
        });
    }

    @Override
    public void deleteOrgan(Long id) {
        // 校验存在
        validateUpdateTenant(id);
        // 删除
        organMapper.deleteById(id);
    }

    private OrganizationDO validateUpdateTenant(Long id) {
        OrganizationDO tenant = organMapper.selectById(id);
        if (tenant == null) {
            throw exception(ORGAN_NOT_EXISTS);
        }
        // 内置组织，不允许删除
        /*if (isSystemTenant(tenant)) {
            throw exception(ORGAN_CAN_NOT_UPDATE_SYSTEM);
        }*/
        return tenant;
    }

    @Override
    public OrganizationDO getOrgan(Long id) {
        return organMapper.selectById(id);
    }

    @Override
    public PageResult<OrganizationDO> getOrganPage(OrganPageReqVO pageReqVO) {
        if(StrUtil.isNotBlank(pageReqVO.getName())) {
            pageReqVO.setPyFirstChar(PinYinUtils.convertFirstChar(pageReqVO.getName()));
            pageReqVO.setPyAll(PinYinUtils.convertToPinyin(pageReqVO.getName()).replaceAll(" ", ""));
        }
        return organMapper.selectPage(pageReqVO);
    }

    @Override
    public OrganizationDO getOrganByName(String name) {
        return organMapper.selectByName(name);
    }

    @Override
    public OrganizationDO getOrganByWebsite(String website) {
        return organMapper.selectByWebsite(website);
    }

    @Override
    public Long getOrganCountByPackageId(Long packageId) {
        return organMapper.selectCountByPackageId(packageId);
    }

    @Override
    public List<OrganizationDO> getOrganListByPackageId(Long packageId) {
        return organMapper.selectListByPackageId(packageId);
    }

    @Override
    public void handleOrganInfo(OrganInfoHandler handler) {
        // 如果禁用，则不执行逻辑
        if (isTenantDisable()) {
            return;
        }
        // 获得组织
        OrganizationDO tenant = getOrgan(OrganContextHolder.getRequiredOrganId());
        // 执行处理器
        handler.handle(tenant);
    }

    @Override
    public void handleOrganMenu(OrganMenuHandler handler) {
        // 如果禁用，则不执行逻辑
        if (isTenantDisable()) {
            return;
        }
        // 获得组织，然后获得菜单
        OrganizationDO tenant = getOrgan(OrganContextHolder.getRequiredOrganId());
        Set<Long> menuIds;

        //menuIds = tenantPackageService.getTenantPackage(tenant.getPackageId()).getMenuIds();

        if (isSystemTenant(tenant)) { // 系统组织，菜单是全量的
            menuIds = CollectionUtils.convertSet(menuService.getMenuList(), MenuDO::getId);
        } else {
            menuIds = tenantPackageService.getTenantPackage(tenant.getPackageId()).getMenuIds();
        }
        // 执行处理器
        handler.handle(menuIds);
    }

    private static boolean isSystemTenant(OrganizationDO tenant) {
        return Objects.equals(tenant.getPackageId(), OrganizationDO.PACKAGE_ID_SYSTEM);
    }

    private boolean isTenantDisable() {
        return organProperties == null || Boolean.FALSE.equals(organProperties.getEnable());
    }

}
