package tech.easyflow.system.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.enums.VisibilityScope;
import tech.easyflow.system.permission.resource.ResourceAccessGrantProvider;
import tech.easyflow.system.permission.resource.VisibilityResource;
import tech.easyflow.system.service.CategoryPermissionService;
import tech.easyflow.system.service.ResourceAccessService;
import tech.easyflow.system.service.SysDeptService;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.Collections;
import java.util.List;

/**
 * 基于租户、创建者、分类授权和可见范围的统一资源权限实现。
 */
@Service
public class ResourceAccessServiceImpl implements ResourceAccessService {

    @Resource
    private CategoryPermissionService categoryPermissionService;

    @Resource
    private SysDeptService sysDeptService;

    @Autowired(required = false)
    private List<ResourceAccessGrantProvider> grantProviders = Collections.emptyList();

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean canAccess(CategoryResourceType resourceType, VisibilityResource resource, ResourceAction action) {
        return canAccess(SaTokenUtil.getLoginAccount(), resourceType, resource, action);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean canAccess(LoginAccount loginAccount, CategoryResourceType resourceType, VisibilityResource resource, ResourceAction action) {
        if (resource == null) {
            return false;
        }
        if (loginAccount == null || loginAccount.getId() == null) {
            return false;
        }
        if (loginAccount.getTenantId() == null || resource.getTenantId() == null
                || !loginAccount.getTenantId().equals(resource.getTenantId())) {
            return false;
        }
        BigInteger accountId = loginAccount.getId();
        // 分享访问需要先完成密钥校验与审计，即使当前账号同时也是资源创建者或超管。
        if (hasExtendedGrant(loginAccount, resourceType, resource, action)) {
            return true;
        }
        if (categoryPermissionService.isSuperAdmin(loginAccount)) {
            return true;
        }
        if (accountId.equals(resource.getCreatedBy())) {
            return true;
        }
        if (ResourceAction.MANAGE == action) {
            return false;
        }
        // Agent 的未分类语义为“全部分类可访问”，只跳过分类白名单，不能跳过可见范围校验。
        boolean agentWithoutCategoryRestriction = CategoryResourceType.AGENT == resourceType
                && resource.getCategoryId() == null;
        if (!agentWithoutCategoryRestriction
                && !categoryPermissionService.canAccessCategory(
                loginAccount, resourceType.getCode(), resource.getCreatedBy(), resource.getCategoryId())) {
            return false;
        }
        VisibilityScope scope = VisibilityScope.fromOrDefault(resource.getVisibilityScope(), VisibilityScope.PRIVATE);
        if (VisibilityScope.PUBLIC == scope) {
            return true;
        }
        if (VisibilityScope.DEPT == scope) {
            return sysDeptService.canUserAccessDeptScopedResource(loginAccount.getDeptId(), resource.getDeptId());
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void assertAccess(CategoryResourceType resourceType, VisibilityResource resource, ResourceAction action, String message) {
        if (!canAccess(resourceType, resource, action)) {
            throw new BusinessException(403, 403, message == null ? "无权限访问该资源" : message);
        }
    }

    /**
     * 判断业务扩展授权是否允许当前动作。
     *
     * @param loginAccount 当前登录账号
     * @param resourceType 资源类型
     * @param resource 资源对象
     * @param action 资源动作
     * @return 任一扩展授权允许时返回 {@code true}
     */
    private boolean hasExtendedGrant(
            LoginAccount loginAccount,
            CategoryResourceType resourceType,
            VisibilityResource resource,
            ResourceAction action
    ) {
        for (ResourceAccessGrantProvider provider : grantProviders) {
            if (provider.grants(loginAccount, resourceType, resource, action)) {
                return true;
            }
        }
        return false;
    }
}
