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.AgentKnowledgeBinding;
import tech.easyflow.agent.mapper.AgentKnowledgeBindingMapper;
import tech.easyflow.agent.mapper.AgentMapper;
import tech.easyflow.agent.service.AgentDependencyAccessService;
import tech.easyflow.agent.service.AgentKnowledgeBindingService;
import tech.easyflow.agent.support.AgentBindingLockExecutor;
import tech.easyflow.ai.rag.KnowledgeRetrievalModes;
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 AgentKnowledgeBindingServiceImpl extends ServiceImpl<AgentKnowledgeBindingMapper, AgentKnowledgeBinding>
        implements AgentKnowledgeBindingService {

    private static final String DEFAULT_RETRIEVAL_MODE = "HYBRID";

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

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<AgentKnowledgeBinding> replaceBindings(BigInteger agentId, List<AgentKnowledgeBinding> bindings) {
        return agentBindingLockExecutor.execute(agentId, () -> {
            Agent agent = requireAgentForUpdate(agentId);
            resourceAccessService.assertAccess(
                    CategoryResourceType.AGENT, agent, ResourceAction.MANAGE, "无权限管理该 Agent");
            List<AgentKnowledgeBinding> current = listAll(agentId);
            if (AgentBindingSemanticComparator.sameKnowledges(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<AgentKnowledgeBinding> 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<AgentKnowledgeBinding> listAll(BigInteger agentId) {
        return list(QueryWrapper.create()
                .where("agent_id = ?", agentId)
                .orderBy("sort_no asc, id asc"));
    }

    /**
     * 从已加载或已写入的绑定中筛选启用项，避免替换后再次查询。
     *
     * @param bindings 知识库绑定
     * @return 启用绑定
     */
    private List<AgentKnowledgeBinding> enabledBindings(List<AgentKnowledgeBinding> 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, AgentKnowledgeBinding binding) {
        if (binding == null || binding.getKnowledgeId() == null) {
            throw new BusinessException("知识库绑定参数不完整");
        }
        KnowledgeRetrievalModes.parse(binding.getRetrievalMode());
        agentDependencyAccessService.requireKnowledge(agent, binding.getKnowledgeId());
    }

    /**
     * 按知识库 ID 的稳定顺序校验绑定并锁定关联资源。
     *
     * @param agent 当前 Agent
     * @param bindings 知识库绑定
     */
    private void validateBindings(Agent agent, List<AgentKnowledgeBinding> bindings) {
        if (bindings == null || bindings.isEmpty()) {
            return;
        }
        Set<BigInteger> knowledgeIds = new LinkedHashSet<>();
        for (AgentKnowledgeBinding binding : bindings) {
            if (binding != null && binding.getKnowledgeId() != null
                    && !knowledgeIds.add(binding.getKnowledgeId())) {
                throw new BusinessException("同一知识库不能重复绑定");
            }
        }
        List<AgentKnowledgeBinding> validationOrder = new ArrayList<>(bindings);
        validationOrder.sort(Comparator.comparing(binding ->
                binding == null || binding.getKnowledgeId() == null
                        ? BigInteger.ZERO
                        : binding.getKnowledgeId()));
        validationOrder.forEach(binding -> validateBinding(agent, binding));
    }

    /**
     * 写入知识库绑定的归属、审计与排序默认值。
     *
     * @param agent 当前 Agent
     * @param binding 知识库绑定
     * @param index 绑定顺序
     */
    private void applyBindingDefaults(Agent agent, AgentKnowledgeBinding binding, int index) {
        LoginAccount account = requireCurrentLoginAccount();
        Date now = new Date();
        binding.setId(null);
        binding.setTenantId(agent.getTenantId());
        binding.setAgentId(agent.getId());
        if (binding.getRetrievalMode() == null || binding.getRetrievalMode().isBlank()) {
            binding.setRetrievalMode(DEFAULT_RETRIEVAL_MODE);
        } else {
            binding.setRetrievalMode(binding.getRetrievalMode().trim().toUpperCase());
        }
        binding.setEnabled(binding.getEnabled() == null || binding.getEnabled());
        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("当前登录状态失效，请重新登录后再试");
        }
    }
}
