package tech.easyflow.ai.plugin.workflow.dependency;

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.util.StringUtils;
import tech.easyflow.ai.entity.Plugin;
import tech.easyflow.ai.entity.PluginItem;
import tech.easyflow.ai.enums.PluginType;
import tech.easyflow.ai.mapper.PluginItemMapper;
import tech.easyflow.ai.mapper.PluginMapper;
import tech.easyflow.ai.service.WorkflowService;
import tech.easyflow.ai.vo.OfflineImpactBindingVo;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 工作流插件依赖分析实现。
 */
@Service
public class WorkflowPluginDependencyServiceImpl implements WorkflowPluginDependencyService {

    private static final String TYPE_PLUGIN = "plugin-node";
    private static final String TYPE_WORKFLOW = "workflow-node";

    private final PluginMapper pluginMapper;
    private final PluginItemMapper pluginItemMapper;
    private final WorkflowService workflowService;

    public WorkflowPluginDependencyServiceImpl(PluginMapper pluginMapper,
                                               PluginItemMapper pluginItemMapper,
                                               WorkflowService workflowService) {
        this.pluginMapper = pluginMapper;
        this.pluginItemMapper = pluginItemMapper;
        this.workflowService = workflowService;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<OfflineImpactBindingVo> listPluginsByWorkflowId(BigInteger workflowId) {
        if (workflowId == null) {
            return Collections.emptyList();
        }
        QueryWrapper wrapper = QueryWrapper.create().eq(Plugin::getWorkflowId, workflowId);
        List<Plugin> plugins = pluginMapper.selectListByQuery(wrapper);
        if (plugins == null || plugins.isEmpty()) {
            return Collections.emptyList();
        }
        List<OfflineImpactBindingVo> result = new ArrayList<>(plugins.size());
        for (Plugin plugin : plugins) {
            if (!PluginType.isWorkflow(plugin.getType())) {
                continue;
            }
            OfflineImpactBindingVo vo = new OfflineImpactBindingVo();
            vo.setId(plugin.getId());
            vo.setTitle(plugin.getName());
            result.add(vo);
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<String> extractWorkflowIdsFromPluginNodes(String content) {
        if (!StringUtils.hasText(content)) {
            return Collections.emptySet();
        }
        Set<String> workflowIds = new LinkedHashSet<>();
        try {
            Object parsed = JSON.parse(content);
            if (!(parsed instanceof JSONObject root)) {
                return workflowIds;
            }
            JSONArray nodes = root.getJSONArray("nodes");
            if (nodes == null) {
                return workflowIds;
            }
            for (int i = 0; i < nodes.size(); i++) {
                JSONObject node = nodes.getJSONObject(i);
                if (node == null || !TYPE_PLUGIN.equals(node.getString("type"))) {
                    continue;
                }
                JSONObject data = node.getJSONObject("data");
                String pluginItemId = data == null ? null : data.getString("pluginId");
                if (!StringUtils.hasText(pluginItemId)) {
                    continue;
                }
                Plugin plugin = getPluginByPluginItemId(pluginItemId);
                if (plugin == null || !PluginType.isWorkflow(plugin.getType()) || plugin.getWorkflowId() == null) {
                    continue;
                }
                workflowIds.add(String.valueOf(plugin.getWorkflowId()));
            }
        } catch (Exception ignored) {
            // ignore
        }
        return workflowIds;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsPluginReference(String content, BigInteger pluginId) {
        if (!StringUtils.hasText(content) || pluginId == null) {
            return false;
        }
        try {
            Object parsed = JSON.parse(content);
            if (!(parsed instanceof JSONObject root)) {
                return false;
            }
            JSONArray nodes = root.getJSONArray("nodes");
            if (nodes == null) {
                return false;
            }
            String expected = String.valueOf(pluginId);
            for (int i = 0; i < nodes.size(); i++) {
                JSONObject node = nodes.getJSONObject(i);
                if (node == null || !TYPE_PLUGIN.equals(node.getString("type"))) {
                    continue;
                }
                JSONObject data = node.getJSONObject("data");
                String pluginItemId = data == null ? null : data.getString("pluginId");
                Plugin plugin = getPluginByPluginItemId(pluginItemId);
                if (plugin != null && expected.equals(String.valueOf(plugin.getId()))) {
                    return true;
                }
            }
        } catch (Exception ignored) {
            // ignore
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsPluginReferenceTransitively(BigInteger workflowId, BigInteger pluginId) {
        if (workflowId == null || pluginId == null) {
            return false;
        }
        return containsPluginReferenceTransitively(
                String.valueOf(workflowId),
                pluginId,
                false,
                new LinkedHashSet<>(),
                new HashMap<>()
        );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsPluginReferenceTransitivelyInPublishedSnapshot(BigInteger workflowId, BigInteger pluginId) {
        if (workflowId == null || pluginId == null) {
            return false;
        }
        return containsPluginReferenceTransitively(
                String.valueOf(workflowId),
                pluginId,
                true,
                new LinkedHashSet<>(),
                new HashMap<>()
        );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Plugin getWorkflowPlugin(BigInteger pluginId) {
        if (pluginId == null) {
            return null;
        }
        Plugin plugin = pluginMapper.selectOneById(pluginId);
        if (plugin == null || !PluginType.isWorkflow(plugin.getType())) {
            return null;
        }
        return plugin;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String resolveWorkflowIdByPluginItemId(String pluginItemId) {
        Plugin plugin = getPluginByPluginItemId(pluginItemId);
        if (plugin == null || !PluginType.isWorkflow(plugin.getType()) || plugin.getWorkflowId() == null) {
            return null;
        }
        return String.valueOf(plugin.getWorkflowId());
    }

    private Plugin getPluginByPluginItemId(String pluginItemId) {
        PluginItem pluginItem = pluginItemMapper.selectOneById(pluginItemId);
        if (pluginItem == null || pluginItem.getPluginId() == null) {
            return null;
        }
        return pluginMapper.selectOneById(pluginItem.getPluginId());
    }

    private boolean containsPluginReferenceTransitively(String workflowId,
                                                        BigInteger pluginId,
                                                        boolean publishedOnly,
                                                        Set<String> visitingWorkflowIds,
                                                        Map<String, Boolean> cache) {
        if (!StringUtils.hasText(workflowId)) {
            return false;
        }
        Boolean cached = cache.get(workflowId);
        if (cached != null) {
            return cached;
        }
        if (!visitingWorkflowIds.add(workflowId)) {
            return false;
        }
        boolean result = false;
        try {
            String workflowContent = resolveWorkflowContent(workflowId, publishedOnly);
            if (!StringUtils.hasText(workflowContent)) {
                cache.put(workflowId, false);
                return false;
            }
            result = containsPluginReferenceTransitivelyInContent(
                    workflowContent,
                    pluginId,
                    publishedOnly,
                    visitingWorkflowIds,
                    cache
            );
            cache.put(workflowId, result);
            return result;
        } finally {
            visitingWorkflowIds.remove(workflowId);
        }
    }

    private boolean containsPluginReferenceTransitivelyInContent(String content,
                                                                 BigInteger pluginId,
                                                                 boolean publishedOnly,
                                                                 Set<String> visitingWorkflowIds,
                                                                 Map<String, Boolean> cache) {
        if (!StringUtils.hasText(content) || pluginId == 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 expectedPluginId = String.valueOf(pluginId);
            for (int i = 0; i < nodes.size(); i++) {
                JSONObject node = nodes.getJSONObject(i);
                if (node == null) {
                    continue;
                }
                String nodeType = node.getString("type");
                JSONObject data = node.getJSONObject("data");
                if (TYPE_PLUGIN.equals(nodeType)) {
                    String pluginItemId = data == null ? null : data.getString("pluginId");
                    Plugin plugin = getPluginByPluginItemId(pluginItemId);
                    if (plugin == null) {
                        continue;
                    }
                    if (expectedPluginId.equals(String.valueOf(plugin.getId()))) {
                        return true;
                    }
                    if (PluginType.isWorkflow(plugin.getType())
                            && plugin.getWorkflowId() != null
                            && containsPluginReferenceTransitively(
                            String.valueOf(plugin.getWorkflowId()),
                            pluginId,
                            publishedOnly,
                            visitingWorkflowIds,
                            cache
                    )) {
                        return true;
                    }
                    continue;
                }
                if (TYPE_WORKFLOW.equals(nodeType)) {
                    String refWorkflowId = data == null ? null : data.getString("workflowId");
                    if (containsPluginReferenceTransitively(
                            refWorkflowId,
                            pluginId,
                            publishedOnly,
                            visitingWorkflowIds,
                            cache
                    )) {
                        return true;
                    }
                }
            }
        } catch (Exception ignored) {
            // ignore
        }
        return false;
    }

    private String resolveWorkflowContent(String workflowId, boolean publishedOnly) {
        tech.easyflow.ai.entity.Workflow workflow = publishedOnly
                ? getPublishedWorkflow(workflowId)
                : workflowService.getById(workflowId);
        return workflow == null ? null : workflow.getContent();
    }

    private tech.easyflow.ai.entity.Workflow getPublishedWorkflow(String workflowId) {
        try {
            return workflowService.getPublishedById(new BigInteger(workflowId));
        } catch (Exception ignored) {
            return null;
        }
    }
}
