package tech.easyflow.system.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Service;
import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysRole;
import tech.easyflow.system.entity.SysRoleCategoryScope;
import tech.easyflow.system.entity.SysRoleCategoryScopeItem;
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
import tech.easyflow.system.enums.CategoryScopeMode;
import tech.easyflow.system.mapper.SysRoleCategoryScopeItemMapper;
import tech.easyflow.system.mapper.SysRoleCategoryScopeMapper;
import tech.easyflow.system.service.CategoryPermissionService;
import tech.easyflow.system.service.SysRoleService;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class CategoryPermissionServiceImpl implements CategoryPermissionService {

    @Resource
    private SysRoleService sysRoleService;

    @Resource
    private SysRoleCategoryScopeMapper sysRoleCategoryScopeMapper;

    @Resource
    private SysRoleCategoryScopeItemMapper sysRoleCategoryScopeItemMapper;

    @Override
    public boolean isCurrentSuperAdmin() {
        if (!StpUtil.isLogin()) {
            return false;
        }
        LoginAccount loginAccount = SaTokenUtil.getLoginAccount();
        return isSuperAdmin(loginAccount);
    }

    @Override
    public boolean isSuperAdmin(LoginAccount loginAccount) {
        return loginAccount != null && isSuperAdmin(loginAccount.getId());
    }

    @Override
    public RoleCategoryAccessSnapshot getCurrentAccess(String resourceType) {
        if (!StpUtil.isLogin()) {
            return new RoleCategoryAccessSnapshot(resourceType, null, false, true, Collections.emptySet());
        }
        LoginAccount loginAccount = SaTokenUtil.getLoginAccount();
        return getAccess(resourceType, loginAccount);
    }

    @Override
    public RoleCategoryAccessSnapshot getAccess(String resourceType, LoginAccount loginAccount) {
        if (loginAccount == null) {
            return new RoleCategoryAccessSnapshot(resourceType, null, false, true, Collections.emptySet());
        }
        BigInteger accountId = loginAccount.getId();
        if (isSuperAdmin(accountId)) {
            return new RoleCategoryAccessSnapshot(resourceType, accountId, true, true, Collections.emptySet());
        }
        List<SysRole> roles = sysRoleService.getRolesByAccountId(accountId);
        if (CollectionUtil.isEmpty(roles)) {
            return new RoleCategoryAccessSnapshot(resourceType, accountId, false, false, Collections.emptySet());
        }
        List<BigInteger> roleIds = roles.stream().map(SysRole::getId).collect(Collectors.toList());
        QueryWrapper scopeWrapper = QueryWrapper.create()
                .in(SysRoleCategoryScope::getRoleId, roleIds)
                .eq(SysRoleCategoryScope::getResourceType, resourceType);
        List<SysRoleCategoryScope> scopes = sysRoleCategoryScopeMapper.selectListByQuery(scopeWrapper);
        if (CollectionUtil.isEmpty(scopes)) {
            return new RoleCategoryAccessSnapshot(resourceType, accountId, false, false, Collections.emptySet());
        }
        boolean allAccess = scopes.stream()
                .anyMatch(item -> CategoryScopeMode.ALL.getCode().equalsIgnoreCase(item.getScopeMode()));
        if (allAccess) {
            return new RoleCategoryAccessSnapshot(resourceType, accountId, false, true, Collections.emptySet());
        }
        List<BigInteger> scopeIds = scopes.stream().map(SysRoleCategoryScope::getId).collect(Collectors.toList());
        if (CollectionUtil.isEmpty(scopeIds)) {
            return new RoleCategoryAccessSnapshot(resourceType, accountId, false, false, Collections.emptySet());
        }
        QueryWrapper itemWrapper = QueryWrapper.create().in(SysRoleCategoryScopeItem::getScopeId, scopeIds);
        List<SysRoleCategoryScopeItem> items = sysRoleCategoryScopeItemMapper.selectListByQuery(itemWrapper);
        Set<BigInteger> categoryIds = items.stream()
                .map(SysRoleCategoryScopeItem::getCategoryId)
                .collect(Collectors.toCollection(LinkedHashSet::new));
        return new RoleCategoryAccessSnapshot(resourceType, accountId, false, false, categoryIds);
    }

    @Override
    public Set<BigInteger> getCurrentVisibleCategoryIds(String resourceType) {
        return getCurrentAccess(resourceType).getCategoryIds();
    }

    @Override
    public boolean canAccessCategory(String resourceType, BigInteger createdBy, BigInteger categoryId) {
        RoleCategoryAccessSnapshot snapshot = getCurrentAccess(resourceType);
        return snapshot.canAccess(createdBy, categoryId);
    }

    @Override
    public boolean canAccessCategory(LoginAccount loginAccount, String resourceType, BigInteger createdBy, BigInteger categoryId) {
        RoleCategoryAccessSnapshot snapshot = getAccess(resourceType, loginAccount);
        return snapshot.canAccess(createdBy, categoryId);
    }

    @Override
    public void assertCategoryResourceVisible(String resourceType, BigInteger createdBy, BigInteger categoryId, String message) {
        if (!canAccessCategory(resourceType, createdBy, categoryId)) {
            throw new BusinessException(message == null ? "无权限访问该资源" : message);
        }
    }

    private boolean isSuperAdmin(BigInteger accountId) {
        List<SysRole> roles = sysRoleService.getRolesByAccountId(accountId);
        return CollectionUtil.isNotEmpty(roles)
                && roles.stream().anyMatch(item -> Constants.SUPER_ADMIN_ROLE_CODE.equals(item.getRoleKey()));
    }
}
