package tech.easyflow.ai.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import tech.easyflow.ai.entity.Bot;
import tech.easyflow.ai.entity.BotDocumentCollection;
import tech.easyflow.ai.entity.BotWorkflow;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.plugin.workflow.dependency.WorkflowPluginDependencyService;
import tech.easyflow.ai.service.BotDocumentCollectionService;
import tech.easyflow.ai.service.BotService;
import tech.easyflow.ai.service.BotWorkflowService;
import tech.easyflow.ai.service.ResourceOfflineImpactService;
import tech.easyflow.ai.service.WorkflowService;
import tech.easyflow.ai.vo.OfflineImpactBindingVo;
import tech.easyflow.ai.vo.OfflineImpactCheckVo;
import tech.easyflow.common.cache.RedisLockExecutor;

import java.math.BigInteger;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * 资源下线影响检查与 Bot 静默解绑实现。
 */
@Service
public class ResourceOfflineImpactServiceImpl implements ResourceOfflineImpactService {

    private static final String KNOWLEDGE_NODE_TYPE = "knowledgeNode";
    private static final String BOT_BINDING_LOCK_KEY_PREFIX = "easyflow:lock:bot:binding:";
    private static final Duration LOCK_WAIT_TIMEOUT = Duration.ofSeconds(2);
    private static final Duration LOCK_LEASE_TIMEOUT = Duration.ofSeconds(10);

    private final BotWorkflowService botWorkflowService;
    private final BotDocumentCollectionService botDocumentCollectionService;
    private final BotService botService;
    private final WorkflowService workflowService;
    private final RedisLockExecutor redisLockExecutor;
    private final WorkflowPluginDependencyService workflowPluginDependencyService;

    public ResourceOfflineImpactServiceImpl(BotWorkflowService botWorkflowService,
                                            BotDocumentCollectionService botDocumentCollectionService,
                                            BotService botService,
                                            WorkflowService workflowService,
                                            RedisLockExecutor redisLockExecutor,
                                            WorkflowPluginDependencyService workflowPluginDependencyService) {
        this.botWorkflowService = botWorkflowService;
        this.botDocumentCollectionService = botDocumentCollectionService;
        this.botService = botService;
        this.workflowService = workflowService;
        this.redisLockExecutor = redisLockExecutor;
        this.workflowPluginDependencyService = workflowPluginDependencyService;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public OfflineImpactCheckVo checkWorkflowImpact(BigInteger workflowId) {
        List<OfflineImpactBindingVo> botBindings = listBotsByWorkflowId(workflowId);
        List<OfflineImpactBindingVo> pluginBindings = workflowPluginDependencyService.listPluginsByWorkflowId(workflowId);
        OfflineImpactCheckVo result = new OfflineImpactCheckVo();
        result.setCanProceed(true);
        result.setBotBindings(botBindings);
        result.setHasBotBindings(!botBindings.isEmpty());
        result.setPluginBindings(pluginBindings);
        result.setHasPluginBindings(!pluginBindings.isEmpty());
        result.setWorkflowUsages(Collections.emptyList());
        result.setHasWorkflowUsages(false);
        result.setMessage(resolveWorkflowOfflineImpactMessage(botBindings, pluginBindings));
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public OfflineImpactCheckVo checkKnowledgeImpact(BigInteger knowledgeId) {
        List<OfflineImpactBindingVo> botBindings = listBotsByKnowledgeId(knowledgeId);
        List<OfflineImpactBindingVo> workflowUsages = listWorkflowsUsingKnowledge(knowledgeId);
        OfflineImpactCheckVo result = new OfflineImpactCheckVo();
        result.setBotBindings(botBindings);
        result.setHasBotBindings(!botBindings.isEmpty());
        result.setWorkflowUsages(workflowUsages);
        result.setHasWorkflowUsages(!workflowUsages.isEmpty());
        result.setCanProceed(workflowUsages.isEmpty());
        result.setMessage(workflowUsages.isEmpty()
                ? (botBindings.isEmpty()
                ? "当前知识库下线后不会影响已有绑定"
                : "当前知识库下线成功后，将自动从相关聊天助手中解绑")
                : "当前知识库仍被工作流使用，请先调整工作流后再下线");
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unbindWorkflowFromBots(BigInteger workflowId) {
        List<BotWorkflow> relations = botWorkflowService.list(QueryWrapper.create()
                .eq(BotWorkflow::getWorkflowId, workflowId));
        Set<BigInteger> botIds = collectBotIds(relations, BotWorkflow::getBotId);
        for (BigInteger botId : botIds) {
            redisLockExecutor.executeWithLock(
                    BOT_BINDING_LOCK_KEY_PREFIX + botId,
                    LOCK_WAIT_TIMEOUT,
                    LOCK_LEASE_TIMEOUT,
                    () -> {
                        botWorkflowService.remove(QueryWrapper.create()
                                .eq(BotWorkflow::getBotId, botId)
                                .eq(BotWorkflow::getWorkflowId, workflowId));
                        trimPublishedSnapshotBindings(botId, "workflowBindings", "workflowId", workflowId);
                    }
            );
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unbindKnowledgeFromBots(BigInteger knowledgeId) {
        List<BotDocumentCollection> relations = botDocumentCollectionService.list(QueryWrapper.create()
                .eq(BotDocumentCollection::getDocumentCollectionId, knowledgeId));
        Set<BigInteger> botIds = collectBotIds(relations, BotDocumentCollection::getBotId);
        for (BigInteger botId : botIds) {
            redisLockExecutor.executeWithLock(
                    BOT_BINDING_LOCK_KEY_PREFIX + botId,
                    LOCK_WAIT_TIMEOUT,
                    LOCK_LEASE_TIMEOUT,
                    () -> {
                        botDocumentCollectionService.remove(QueryWrapper.create()
                                .eq(BotDocumentCollection::getBotId, botId)
                                .eq(BotDocumentCollection::getDocumentCollectionId, knowledgeId));
                        trimPublishedSnapshotBindings(botId, "knowledgeBindings", "knowledgeId", knowledgeId);
                    }
            );
        }
    }

    private List<OfflineImpactBindingVo> listBotsByWorkflowId(BigInteger workflowId) {
        List<BotWorkflow> relations = botWorkflowService.list(QueryWrapper.create()
                .eq(BotWorkflow::getWorkflowId, workflowId));
        return listBotsByIds(collectBotIds(relations, BotWorkflow::getBotId));
    }

    private List<OfflineImpactBindingVo> listBotsByKnowledgeId(BigInteger knowledgeId) {
        List<BotDocumentCollection> relations = botDocumentCollectionService.list(QueryWrapper.create()
                .eq(BotDocumentCollection::getDocumentCollectionId, knowledgeId));
        return listBotsByIds(collectBotIds(relations, BotDocumentCollection::getBotId));
    }

    private List<OfflineImpactBindingVo> listBotsByIds(Set<BigInteger> botIds) {
        if (botIds.isEmpty()) {
            return Collections.emptyList();
        }
        List<Bot> bots = botService.listByIds(botIds);
        Map<BigInteger, Bot> botMap = new HashMap<>();
        for (Bot bot : bots) {
            botMap.put(bot.getId(), bot);
        }
        List<OfflineImpactBindingVo> result = new ArrayList<>(botIds.size());
        for (BigInteger botId : botIds) {
            Bot bot = botMap.get(botId);
            if (bot == null) {
                continue;
            }
            result.add(toBindingVo(bot.getId(), bot.getTitle()));
        }
        return result;
    }

    private List<OfflineImpactBindingVo> listWorkflowsUsingKnowledge(BigInteger knowledgeId) {
        List<Workflow> workflows = workflowService.list();
        if (workflows == null || workflows.isEmpty()) {
            return Collections.emptyList();
        }
        List<OfflineImpactBindingVo> result = new ArrayList<>();
        for (Workflow workflow : workflows) {
            if (workflow == null || workflow.getId() == null) {
                continue;
            }
            if (containsKnowledgeReference(workflow.getContent(), knowledgeId)) {
                result.add(toBindingVo(workflow.getId(), workflow.getTitle()));
            }
        }
        return result;
    }

    private String resolveWorkflowOfflineImpactMessage(List<OfflineImpactBindingVo> botBindings,
                                                       List<OfflineImpactBindingVo> pluginBindings) {
        if (!pluginBindings.isEmpty() && !botBindings.isEmpty()) {
            return "当前工作流被插件和聊天助手引用，下线后插件将不可用，聊天助手将自动解绑";
        }
        if (!pluginBindings.isEmpty()) {
            return "当前工作流被插件引用，下线后相关插件将不可用";
        }
        if (!botBindings.isEmpty()) {
            return "当前工作流下线成功后，将自动从相关聊天助手中解绑";
        }
        return "当前工作流下线后不会影响已有绑定";
    }

    private boolean containsKnowledgeReference(String content, BigInteger knowledgeId) {
        if (!StringUtils.hasText(content) || knowledgeId == null) {
            return false;
        }
        try {
            Object parsed = JSON.parse(content);
            if (!(parsed instanceof JSONObject root)) {
                return false;
            }
            JSONArray nodes = root.getJSONArray("nodes");
            if (nodes == null || nodes.isEmpty()) {
                return false;
            }
            String expected = knowledgeId.toString();
            for (int i = 0; i < nodes.size(); i++) {
                JSONObject node = nodes.getJSONObject(i);
                if (node == null || !KNOWLEDGE_NODE_TYPE.equals(node.getString("type"))) {
                    continue;
                }
                JSONObject data = node.getJSONObject("data");
                if (data == null) {
                    continue;
                }
                Object rawKnowledgeId = data.get("knowledgeId");
                if (rawKnowledgeId != null && expected.equals(String.valueOf(rawKnowledgeId))) {
                    return true;
                }
            }
            return false;
        } catch (Exception ignored) {
            return false;
        }
    }

    private void trimPublishedSnapshotBindings(BigInteger botId,
                                               String bindingsKey,
                                               String idKey,
                                               BigInteger resourceId) {
        Bot bot = botService.getById(botId);
        if (bot == null || bot.getPublishedSnapshotJson() == null || bot.getPublishedSnapshotJson().isEmpty()) {
            return;
        }
        Map<String, Object> snapshot = new LinkedHashMap<>(bot.getPublishedSnapshotJson());
        Object rawBindings = snapshot.get(bindingsKey);
        if (!(rawBindings instanceof List<?> bindings)) {
            return;
        }

        List<Map<String, Object>> filtered = new ArrayList<>();
        boolean changed = false;
        String expectedId = resourceId == null ? null : resourceId.toString();
        for (Object item : bindings) {
            if (!(item instanceof Map<?, ?> bindingMap)) {
                continue;
            }
            Object currentId = bindingMap.get(idKey);
            if (expectedId != null && currentId != null && expectedId.equals(String.valueOf(currentId))) {
                changed = true;
                continue;
            }
            filtered.add(new LinkedHashMap<>((Map<String, Object>) bindingMap));
        }
        if (!changed) {
            return;
        }
        snapshot.put(bindingsKey, filtered);
        Bot update = new Bot();
        update.setId(botId);
        update.setPublishedSnapshotJson(snapshot);
        botService.updateById(update);
    }

    private <T> Set<BigInteger> collectBotIds(Collection<T> relations, BotIdGetter<T> getter) {
        if (relations == null || relations.isEmpty()) {
            return Collections.emptySet();
        }
        Set<BigInteger> result = new LinkedHashSet<>();
        for (T relation : relations) {
            BigInteger botId = getter.getBotId(relation);
            if (botId != null) {
                result.add(botId);
            }
        }
        return result;
    }

    private OfflineImpactBindingVo toBindingVo(BigInteger id, String title) {
        OfflineImpactBindingVo vo = new OfflineImpactBindingVo();
        vo.setId(id);
        vo.setTitle(title);
        return vo;
    }

    @FunctionalInterface
    private interface BotIdGetter<T> {

        BigInteger getBotId(T relation);
    }
}
