package tech.easyflow.system.permission.resource;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
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.ResourceLookup;
import tech.easyflow.system.service.CategoryPermissionService;
import tech.easyflow.system.service.ResourceAccessService;

import java.lang.reflect.Method;
import java.util.List;

@Aspect
@Component
public class RequireResourceAccessAspect {

    private final List<ResourceAccessResolver> resolvers;
    private final ResourceAccessService resourceAccessService;
    private final CategoryPermissionService categoryPermissionService;
    private final ExpressionParser expressionParser = new SpelExpressionParser();
    private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    public RequireResourceAccessAspect(List<ResourceAccessResolver> resolvers,
                                       ResourceAccessService resourceAccessService,
                                       CategoryPermissionService categoryPermissionService) {
        this.resolvers = resolvers;
        this.resourceAccessService = resourceAccessService;
        this.categoryPermissionService = categoryPermissionService;
    }

    @Around("@annotation(requireResourceAccess)")
    public Object doAround(ProceedingJoinPoint joinPoint, RequireResourceAccess requireResourceAccess) throws Throwable {
        Object identifier = resolveIdentifier(joinPoint, requireResourceAccess);
        ResourceAccessResolver resolver = findResolver(requireResourceAccess.resource(), requireResourceAccess.lookup());
        ResolvedResourceAccess resolved = resolver.resolve(identifier);
        assertExecutionOwner(resolved);
        resourceAccessService.assertAccess(
                requireResourceAccess.resource(),
                resolved.getResource(),
                requireResourceAccess.action(),
                requireResourceAccess.denyMessage()
        );
        return joinPoint.proceed();
    }

    private Object resolveIdentifier(ProceedingJoinPoint joinPoint, RequireResourceAccess requireResourceAccess) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        MethodBasedEvaluationContext context = new MethodBasedEvaluationContext(
                joinPoint.getTarget(),
                method,
                joinPoint.getArgs(),
                parameterNameDiscoverer
        );
        return expressionParser.parseExpression(requireResourceAccess.idExpr()).getValue(context);
    }

    private ResourceAccessResolver findResolver(CategoryResourceType resourceType, ResourceLookup lookup) {
        return resolvers.stream()
                .filter(item -> item.supports(resourceType, lookup))
                .findFirst()
                .orElseThrow(() -> new IllegalStateException("未找到资源访问解析器: " + resourceType + "/" + lookup));
    }

    private void assertExecutionOwner(ResolvedResourceAccess resolved) {
        String executionOwnerKey = resolved.getExecutionOwnerKey();
        if (executionOwnerKey == null || executionOwnerKey.isBlank() || categoryPermissionService.isCurrentSuperAdmin()) {
            return;
        }
        LoginAccount loginAccount = SaTokenUtil.getLoginAccount();
        String accountId = loginAccount == null || loginAccount.getId() == null ? null : loginAccount.getId().toString();
        if (!executionOwnerKey.equals(accountId)) {
            throw new BusinessException("无权限访问该执行记录");
        }
    }
}
