package tech.easyflow.agent.service;

import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Service;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentCategory;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.entity.Mcp;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.entity.Plugin;
import tech.easyflow.ai.entity.PluginItem;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.ai.mapper.PluginMapper;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.ai.service.McpService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.ai.service.PluginItemService;
import tech.easyflow.ai.service.PluginVisibilityService;
import tech.easyflow.ai.service.WorkflowService;
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.vo.RoleCategoryAccessSnapshot;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.service.CategoryPermissionService;
import tech.easyflow.system.service.ResourceAccessService;
import tech.easyflow.skill.entity.Skill;
import tech.easyflow.skill.service.SkillService;

import java.math.BigInteger;
import java.util.Objects;

/**
 * Agent 依赖资源的租户、权限和可用性校验服务。
 */
@Service
public class AgentDependencyAccessService {

    private final ModelService modelService;
    private final WorkflowService workflowService;
    private final PluginItemService pluginItemService;
    private final PluginMapper pluginMapper;
    private final PluginVisibilityService pluginVisibilityService;
    private final McpService mcpService;
    private final DocumentCollectionService documentCollectionService;
    private final AgentCategoryService agentCategoryService;
    private final CategoryPermissionService categoryPermissionService;
    private final ResourceAccessService resourceAccessService;
    private final SkillService skillService;

    /**
     * 创建 Agent 依赖资源校验服务。
     *
     * @param modelService 模型服务
     * @param workflowService 工作流服务
     * @param pluginItemService 插件工具服务
     * @param pluginMapper 插件 Mapper
     * @param pluginVisibilityService 插件可见性服务
     * @param mcpService MCP 服务
     * @param documentCollectionService 知识库服务
     * @param agentCategoryService Agent 分类服务
     * @param categoryPermissionService 分类权限服务
     * @param resourceAccessService 资源权限服务
     * @param skillService Skill 服务
     */
    public AgentDependencyAccessService(ModelService modelService,
                                        WorkflowService workflowService,
                                        PluginItemService pluginItemService,
                                        PluginMapper pluginMapper,
                                        PluginVisibilityService pluginVisibilityService,
                                        McpService mcpService,
                                        DocumentCollectionService documentCollectionService,
                                        AgentCategoryService agentCategoryService,
                                        CategoryPermissionService categoryPermissionService,
                                        ResourceAccessService resourceAccessService,
                                        SkillService skillService) {
        this.modelService = modelService;
        this.workflowService = workflowService;
        this.pluginItemService = pluginItemService;
        this.pluginMapper = pluginMapper;
        this.pluginVisibilityService = pluginVisibilityService;
        this.mcpService = mcpService;
        this.documentCollectionService = documentCollectionService;
        this.agentCategoryService = agentCategoryService;
        this.categoryPermissionService = categoryPermissionService;
        this.resourceAccessService = resourceAccessService;
        this.skillService = skillService;
    }

    /**
     * 校验 Agent 模型并锁定模型行。
     *
     * @param agent Agent
     * @param modelId 模型 ID
     * @return 模型
     */
    public Model requireModel(Agent agent, BigInteger modelId) {
        if (modelId == null) {
            throw new BusinessException("Agent 模型不能为空");
        }
        Model model = modelService.getOne(QueryWrapper.create()
                .eq(Model::getId, modelId)
                .forUpdate());
        if (model == null) {
            throw new BusinessException("Agent 模型不存在");
        }
        assertSameTenant(agent, model.getTenantId(), "无权限使用该模型");
        if (!Model.MODEL_TYPES[0].equals(model.getModelType())) {
            throw new BusinessException("Agent 仅支持聊天模型");
        }
        return model;
    }

    /**
     * 校验并锁定工作流。
     *
     * @param agent Agent
     * @param workflowId 工作流 ID
     * @return 已发布工作流
     */
    public Workflow requireWorkflow(Agent agent, BigInteger workflowId) {
        Workflow workflow = workflowService.getOne(QueryWrapper.create()
                .eq(Workflow::getId, workflowId)
                .forUpdate());
        if (workflow == null || PublishStatus.from(workflow.getPublishStatus()) != PublishStatus.PUBLISHED) {
            throw new BusinessException("绑定工作流不存在或未发布");
        }
        assertSameTenant(agent, workflow.getTenantId(), "无权限绑定该工作流");
        resourceAccessService.assertAccess(
                CategoryResourceType.WORKFLOW, workflow, ResourceAction.USE, "无权限绑定该工作流");
        return workflow;
    }

    /**
     * 校验插件工具及其所属插件，并按父子顺序锁定资源行。
     *
     * @param agent Agent
     * @param pluginItemId 插件工具 ID
     * @return 插件工具
     */
    public PluginItem requirePluginItem(Agent agent, BigInteger pluginItemId) {
        return requirePluginResource(agent, pluginItemId).pluginItem();
    }

    /**
     * 校验并锁定插件工具及其父插件，返回同一事务中的完整调用资源。
     *
     * @param agent Agent
     * @param pluginItemId 插件工具 ID
     * @return 插件项与父插件
     */
    public PluginResource requirePluginResource(Agent agent, BigInteger pluginItemId) {
        PluginItem current = pluginItemService.getById(pluginItemId);
        if (current == null || current.getPluginId() == null) {
            throw new BusinessException("绑定插件不存在");
        }
        Plugin plugin = pluginMapper.selectOneByQuery(QueryWrapper.create()
                .eq(Plugin::getId, current.getPluginId())
                .forUpdate());
        PluginItem pluginItem = pluginItemService.getOne(QueryWrapper.create()
                .eq(PluginItem::getId, pluginItemId)
                .forUpdate());
        if (plugin == null || pluginItem == null || !Objects.equals(plugin.getId(), pluginItem.getPluginId())) {
            throw new BusinessException("绑定插件不存在");
        }
        if (!Integer.valueOf(1).equals(pluginItem.getStatus())) {
            throw new BusinessException("绑定插件未启用");
        }
        assertSameTenant(agent, plugin.getTenantId(), "无权限绑定该插件");
        pluginVisibilityService.assertPluginVisible(plugin.getCreatedBy(), plugin.getId(), "无权限绑定该插件");
        return new PluginResource(pluginItem, plugin);
    }

    /**
     * 校验并锁定 MCP。
     *
     * @param agent Agent
     * @param mcpId MCP ID
     * @return MCP
     */
    public Mcp requireMcp(Agent agent, BigInteger mcpId) {
        Mcp mcp = mcpService.getOne(QueryWrapper.create()
                .eq(Mcp::getId, mcpId)
                .forUpdate());
        if (mcp == null || !Boolean.TRUE.equals(mcp.getStatus())) {
            throw new BusinessException("绑定 MCP 不存在或未启用");
        }
        assertSameTenant(agent, mcp.getTenantId(), "无权限绑定该 MCP");
        return mcp;
    }

    /**
     * 校验并锁定 Agent 可使用的已发布 Skill。
     *
     * <p>Skill 发布阶段已经完成底层 Tool 权限与 MCP 清单检测。Agent 保存阶段只消费冻结快照，
     * 避免在数据库事务中执行外部 MCP I/O；快照内容及组合 hash 由运行投影器继续校验。</p>
     *
     * @param agent Agent
     * @param skillId Skill ID
     * @return 已发布 Skill
     */
    public Skill requireSkill(Agent agent, BigInteger skillId) {
        Skill skill = skillService.getOne(QueryWrapper.create()
                .eq(Skill::getId, skillId)
                .forUpdate());
        if (skill == null || PublishStatus.from(skill.getPublishStatus()) != PublishStatus.PUBLISHED
                || skill.getPublishedSnapshotJson() == null || skill.getPublishedSnapshotJson().isEmpty()) {
            throw new BusinessException("绑定 Skill 不存在或未发布");
        }
        assertSameTenant(agent, skill.getTenantId(), "无权限绑定该 Skill");
        resourceAccessService.assertAccess(
                CategoryResourceType.SKILL, skill, ResourceAction.USE, "无权限绑定该 Skill");
        return skill;
    }

    /**
     * 校验并锁定知识库。
     *
     * @param agent Agent
     * @param knowledgeId 知识库 ID
     * @return 已发布知识库
     */
    public DocumentCollection requireKnowledge(Agent agent, BigInteger knowledgeId) {
        DocumentCollection knowledge = documentCollectionService.getOne(QueryWrapper.create()
                .eq(DocumentCollection::getId, knowledgeId)
                .forUpdate());
        if (knowledge == null || PublishStatus.from(knowledge.getPublishStatus()) != PublishStatus.PUBLISHED) {
            throw new BusinessException("绑定知识库不存在或未发布");
        }
        assertSameTenant(agent, knowledge.getTenantId(), "无权限绑定该知识库");
        resourceAccessService.assertAccess(
                CategoryResourceType.KNOWLEDGE, knowledge, ResourceAction.USE, "无权限绑定该知识库");
        return knowledge;
    }

    /**
     * 校验 Agent 分类属于当前租户并在当前账号授权范围内。
     *
     * @param agent Agent
     */
    public void validateCategory(Agent agent) {
        if (agent == null || agent.getCategoryId() == null) {
            return;
        }
        AgentCategory category = agentCategoryService.getById(agent.getCategoryId());
        if (category == null || !Integer.valueOf(1).equals(category.getStatus())) {
            throw new BusinessException("Agent 分类不存在或未启用");
        }
        assertSameTenant(agent, category.getTenantId(), "无权限使用该 Agent 分类");
        RoleCategoryAccessSnapshot access =
                categoryPermissionService.getCurrentAccess(CategoryResourceType.AGENT.getCode());
        if (access.isRestricted() && !access.getCategoryIds().contains(agent.getCategoryId())) {
            throw new BusinessException("无权限使用该 Agent 分类");
        }
    }

    /**
     * 校验资源与 Agent 属于同一租户。
     *
     * @param agent Agent
     * @param resourceTenantId 资源租户 ID
     * @param message 拒绝消息
     */
    private void assertSameTenant(Agent agent, Object resourceTenantId, String message) {
        BigInteger agentTenantId = agent == null ? null : agent.getTenantId();
        if (agentTenantId == null) {
            LoginAccount account = SaTokenUtil.getLoginAccount();
            agentTenantId = account == null ? null : account.getTenantId();
        }
        if (agentTenantId == null || resourceTenantId == null
                || !agentTenantId.toString().equals(String.valueOf(resourceTenantId))) {
            throw new BusinessException(message);
        }
    }

    /**
     * 插件运行依赖聚合。
     *
     * @param pluginItem 插件工具
     * @param plugin 父插件调用配置
     */
    public record PluginResource(PluginItem pluginItem, Plugin plugin) {
    }
}
