package tech.easyflow.agent.service.impl;

import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentToolBinding;
import tech.easyflow.agent.enums.AgentToolType;
import tech.easyflow.agent.mapper.AgentMapper;
import tech.easyflow.agent.mapper.AgentToolBindingMapper;
import tech.easyflow.agent.service.AgentDependencyAccessService;
import tech.easyflow.agent.service.AgentToolBindingService;
import tech.easyflow.agent.support.AgentBindingLockExecutor;
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.service.ResourceAccessService;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * Agent 工具绑定服务实现。
 */
@Service
public class AgentToolBindingServiceImpl extends ServiceImpl<AgentToolBindingMapper, AgentToolBinding>
        implements AgentToolBindingService {

    @Resource
    private AgentMapper agentMapper;
    @Resource
    private ResourceAccessService resourceAccessService;
    @Resource
    private AgentBindingLockExecutor agentBindingLockExecutor;
    @Resource
    private AgentDependencyAccessService agentDependencyAccessService;

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<AgentToolBinding> replaceBindings(BigInteger agentId, List<AgentToolBinding> bindings) {
        return agentBindingLockExecutor.execute(agentId, () -> {
            Agent agent = requireAgentForUpdate(agentId);
            resourceAccessService.assertAccess(
                    CategoryResourceType.AGENT, agent, ResourceAction.MANAGE, "无权限管理该 Agent");
            List<AgentToolBinding> current = listAll(agentId);
            if (AgentBindingSemanticComparator.sameTools(current, bindings)) {
                return enabledBindings(current);
            }
            validateBindings(agent, bindings);
            remove(QueryWrapper.create().where("agent_id = ?", agentId));
            if (bindings == null || bindings.isEmpty()) {
                return Collections.emptyList();
            }
            for (int i = 0; i < bindings.size(); i++) {
                applyBindingDefaults(agent, bindings.get(i), i);
            }
            saveBatch(bindings);
            return enabledBindings(bindings);
        });
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<AgentToolBinding> listEnabled(BigInteger agentId) {
        return list(QueryWrapper.create()
                .where("agent_id = ?", agentId)
                .and("enabled = ?", true)
                .orderBy("sort_no asc, id asc"));
    }

    /**
     * 查询 Agent 的全部工具绑定，用于整组语义比较。
     *
     * @param agentId Agent ID
     * @return 稳定排序的全部绑定
     */
    private List<AgentToolBinding> listAll(BigInteger agentId) {
        return list(QueryWrapper.create()
                .where("agent_id = ?", agentId)
                .orderBy("sort_no asc, id asc"));
    }

    /**
     * 从已加载或已写入的绑定中筛选启用项，避免替换后再次查询。
     *
     * @param bindings 工具绑定
     * @return 启用绑定
     */
    private List<AgentToolBinding> enabledBindings(List<AgentToolBinding> bindings) {
        return bindings.stream()
                .filter(binding -> binding != null && binding.getEnabled() != Boolean.FALSE)
                .toList();
    }

    /**
     * 锁定并加载待修改的 Agent。
     *
     * @param agentId Agent ID
     * @return 已锁定 Agent
     * @throws BusinessException Agent 不存在时抛出
     */
    private Agent requireAgentForUpdate(BigInteger agentId) {
        Agent agent = agentMapper.selectOneByQuery(QueryWrapper.create()
                .eq(Agent::getId, agentId)
                .forUpdate());
        if (agent == null) {
            throw new BusinessException("Agent 不存在");
        }
        return agent;
    }

    /**
     * 校验单个工具绑定，并锁定目标资源到当前事务结束。
     *
     * @param agent 当前 Agent
     * @param binding 工具绑定
     * @throws BusinessException 绑定参数无效或目标资源不可用时抛出
     */
    private void validateBinding(Agent agent, AgentToolBinding binding) {
        if (binding == null || binding.getTargetId() == null || binding.getToolType() == null) {
            throw new BusinessException("工具绑定参数不完整");
        }
        AgentToolType type = AgentToolType.from(binding.getToolType());
        if (type == AgentToolType.WORKFLOW) {
            agentDependencyAccessService.requireWorkflow(agent, binding.getTargetId());
            return;
        }
        if (type == AgentToolType.PLUGIN) {
            agentDependencyAccessService.requirePluginItem(agent, binding.getTargetId());
            return;
        }
        agentDependencyAccessService.requireMcp(agent, binding.getTargetId());
    }

    /**
     * 按稳定顺序校验绑定并锁定关联资源，降低并发替换产生数据库死锁的概率。
     *
     * @param agent 当前 Agent
     * @param bindings 工具绑定
     */
    private void validateBindings(Agent agent, List<AgentToolBinding> bindings) {
        if (bindings == null || bindings.isEmpty()) {
            return;
        }
        Set<String> resourceKeys = new LinkedHashSet<>();
        Set<String> declaredToolNames = new LinkedHashSet<>();
        for (AgentToolBinding binding : bindings) {
            if (binding == null || binding.getToolType() == null || binding.getTargetId() == null) {
                continue;
            }
            String resourceKey = AgentToolType.from(binding.getToolType()).name() + ":" + binding.getTargetId();
            if (!resourceKeys.add(resourceKey)) {
                throw new BusinessException("同一工具资源不能重复绑定");
            }
            String toolName = binding.getToolName();
            if (toolName != null && !toolName.isBlank() && !declaredToolNames.add(toolName.trim())) {
                throw new BusinessException("Agent 工具运行名冲突：" + toolName.trim() + "，请调整工具名称");
            }
        }
        List<AgentToolBinding> validationOrder = new ArrayList<>(bindings);
        validationOrder.sort(Comparator
                .comparing((AgentToolBinding binding) ->
                        binding == null || binding.getToolType() == null ? "" : binding.getToolType())
                .thenComparing(binding ->
                        binding == null || binding.getTargetId() == null
                                ? BigInteger.ZERO
                                : binding.getTargetId()));
        validationOrder.forEach(binding -> validateBinding(agent, binding));
    }

    /**
     * 写入工具绑定的归属、审计与排序默认值。
     *
     * @param agent 当前 Agent
     * @param binding 工具绑定
     * @param index 绑定顺序
     */
    private void applyBindingDefaults(Agent agent, AgentToolBinding binding, int index) {
        LoginAccount account = requireCurrentLoginAccount();
        Date now = new Date();
        binding.setId(null);
        binding.setTenantId(agent.getTenantId());
        binding.setAgentId(agent.getId());
        binding.setEnabled(binding.getEnabled() == null || binding.getEnabled());
        binding.setHitlEnabled(Boolean.TRUE.equals(binding.getHitlEnabled()));
        binding.setSortNo(binding.getSortNo() == null ? index : binding.getSortNo());
        binding.setCreated(now);
        binding.setCreatedBy(account.getId());
        binding.setModified(now);
        binding.setModifiedBy(account.getId());
    }

    /**
     * 获取当前登录账号。
     *
     * @return 当前登录账号
     * @throws BusinessException 登录信息失效时抛出
     */
    private LoginAccount requireCurrentLoginAccount() {
        try {
            LoginAccount account = SaTokenUtil.getLoginAccount();
            if (account == null || account.getId() == null) {
                throw new BusinessException("当前登录状态失效，请重新登录后再试");
            }
            return account;
        } catch (Exception e) {
            if (e instanceof BusinessException businessException) {
                throw businessException;
            }
            throw new BusinessException("当前登录状态失效，请重新登录后再试");
        }
    }
}
