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

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.ai.entity.Plugin;
import tech.easyflow.ai.entity.PluginItem;
import tech.easyflow.ai.entity.Workflow;
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 java.lang.reflect.Proxy;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;

public class WorkflowPluginDependencyServiceImplTest {

    @Test
    public void testPublishedCheckShouldIgnoreUnpublishedDraftCycle() {
        WorkflowPluginDependencyServiceImpl service = newService(
                workflows(
                        workflowVariant(rootWorkflowContent("2"), rootWorkflowContent("2")),
                        workflowVariant(pluginWorkflowContent("700"), terminalWorkflowContent())
                ),
                plugins(),
                pluginItems()
        );

        Assert.assertTrue(service.containsPluginReferenceTransitively(BigInteger.ONE, BigInteger.valueOf(900)));
        Assert.assertFalse(service.containsPluginReferenceTransitivelyInPublishedSnapshot(BigInteger.ONE, BigInteger.valueOf(900)));
    }

    @Test
    public void testPublishedCheckShouldBlockPublishedCycleEvenWhenDraftClean() {
        WorkflowPluginDependencyServiceImpl service = newService(
                workflows(
                        workflowVariant(rootWorkflowContent("2"), rootWorkflowContent("2")),
                        workflowVariant(terminalWorkflowContent(), pluginWorkflowContent("700"))
                ),
                plugins(),
                pluginItems()
        );

        Assert.assertFalse(service.containsPluginReferenceTransitively(BigInteger.ONE, BigInteger.valueOf(900)));
        Assert.assertTrue(service.containsPluginReferenceTransitivelyInPublishedSnapshot(BigInteger.ONE, BigInteger.valueOf(900)));
    }

    private static WorkflowPluginDependencyServiceImpl newService(Map<String, WorkflowVariant> workflowStore,
                                                                 Map<String, Plugin> pluginStore,
                                                                 Map<String, PluginItem> pluginItemStore) {
        return new WorkflowPluginDependencyServiceImpl(
                mockPluginMapper(pluginStore),
                mockPluginItemMapper(pluginItemStore),
                mockWorkflowService(workflowStore)
        );
    }

    private static Map<String, WorkflowVariant> workflows(WorkflowVariant root, WorkflowVariant child) {
        Map<String, WorkflowVariant> workflows = new HashMap<>();
        workflows.put("1", root);
        workflows.put("2", child);
        return workflows;
    }

    private static Map<String, Plugin> plugins() {
        Map<String, Plugin> plugins = new HashMap<>();
        Plugin plugin = new Plugin();
        plugin.setId(BigInteger.valueOf(900));
        plugin.setType(PluginType.WORKFLOW.getCode());
        plugin.setWorkflowId(BigInteger.valueOf(30));
        plugins.put("900", plugin);
        return plugins;
    }

    private static Map<String, PluginItem> pluginItems() {
        Map<String, PluginItem> pluginItems = new HashMap<>();
        PluginItem pluginItem = new PluginItem();
        pluginItem.setId(BigInteger.valueOf(700));
        pluginItem.setPluginId(BigInteger.valueOf(900));
        pluginItems.put("700", pluginItem);
        return pluginItems;
    }

    private static WorkflowService mockWorkflowService(Map<String, WorkflowVariant> workflowStore) {
        return (WorkflowService) Proxy.newProxyInstance(
                WorkflowService.class.getClassLoader(),
                new Class[]{WorkflowService.class},
                (proxy, method, args) -> {
                    String methodName = method.getName();
                    if ("getById".equals(methodName)) {
                        return buildWorkflow(workflowStore, args == null ? null : args[0], false);
                    }
                    if ("getPublishedById".equals(methodName)) {
                        return buildWorkflow(workflowStore, args == null ? null : args[0], true);
                    }
                    if ("equals".equals(methodName)) {
                        return proxy == args[0];
                    }
                    if ("hashCode".equals(methodName)) {
                        return System.identityHashCode(proxy);
                    }
                    if (method.getReturnType() == boolean.class) {
                        return false;
                    }
                    if (method.getReturnType() == int.class) {
                        return 0;
                    }
                    if (method.getReturnType() == long.class) {
                        return 0L;
                    }
                    return null;
                }
        );
    }

    private static PluginMapper mockPluginMapper(Map<String, Plugin> pluginStore) {
        return (PluginMapper) Proxy.newProxyInstance(
                PluginMapper.class.getClassLoader(),
                new Class[]{PluginMapper.class},
                (proxy, method, args) -> {
                    if ("selectOneById".equals(method.getName())) {
                        return pluginStore.get(String.valueOf(args[0]));
                    }
                    if ("equals".equals(method.getName())) {
                        return proxy == args[0];
                    }
                    if ("hashCode".equals(method.getName())) {
                        return System.identityHashCode(proxy);
                    }
                    if (method.getReturnType() == boolean.class) {
                        return false;
                    }
                    if (method.getReturnType() == int.class) {
                        return 0;
                    }
                    if (method.getReturnType() == long.class) {
                        return 0L;
                    }
                    return null;
                }
        );
    }

    private static PluginItemMapper mockPluginItemMapper(Map<String, PluginItem> pluginItemStore) {
        return (PluginItemMapper) Proxy.newProxyInstance(
                PluginItemMapper.class.getClassLoader(),
                new Class[]{PluginItemMapper.class},
                (proxy, method, args) -> {
                    if ("selectOneById".equals(method.getName())) {
                        return pluginItemStore.get(String.valueOf(args[0]));
                    }
                    if ("equals".equals(method.getName())) {
                        return proxy == args[0];
                    }
                    if ("hashCode".equals(method.getName())) {
                        return System.identityHashCode(proxy);
                    }
                    if (method.getReturnType() == boolean.class) {
                        return false;
                    }
                    if (method.getReturnType() == int.class) {
                        return 0;
                    }
                    if (method.getReturnType() == long.class) {
                        return 0L;
                    }
                    return null;
                }
        );
    }

    private static Workflow buildWorkflow(Map<String, WorkflowVariant> workflowStore, Object idValue, boolean published) {
        if (idValue == null) {
            return null;
        }
        WorkflowVariant variant = workflowStore.get(String.valueOf(idValue));
        if (variant == null) {
            return null;
        }
        Workflow workflow = new Workflow();
        try {
            workflow.setId(new BigInteger(String.valueOf(idValue)));
        } catch (Exception ignored) {
            workflow.setId(null);
        }
        workflow.setContent(published ? variant.publishedContent : variant.draftContent);
        return workflow;
    }

    private static WorkflowVariant workflowVariant(String draftContent, String publishedContent) {
        WorkflowVariant variant = new WorkflowVariant();
        variant.draftContent = draftContent;
        variant.publishedContent = publishedContent;
        return variant;
    }

    private static String rootWorkflowContent(String childWorkflowId) {
        return workflowJson(array(workflowNode("wf-1", childWorkflowId)));
    }

    private static String pluginWorkflowContent(String pluginItemId) {
        return workflowJson(array(pluginNode("plugin-1", pluginItemId)));
    }

    private static String terminalWorkflowContent() {
        return workflowJson(new JSONArray());
    }

    private static String workflowJson(JSONArray nodes) {
        JSONObject root = new JSONObject();
        root.put("nodes", nodes);
        root.put("edges", new JSONArray());
        return root.toJSONString();
    }

    private static JSONArray array(JSONObject... objects) {
        JSONArray array = new JSONArray();
        for (JSONObject object : objects) {
            array.add(object);
        }
        return array;
    }

    private static JSONObject workflowNode(String id, String workflowId) {
        JSONObject data = new JSONObject();
        data.put("workflowId", workflowId);
        return node(id, "workflow-node", data);
    }

    private static JSONObject pluginNode(String id, String pluginItemId) {
        JSONObject data = new JSONObject();
        data.put("pluginId", pluginItemId);
        return node(id, "plugin-node", data);
    }

    private static JSONObject node(String id, String type, JSONObject data) {
        JSONObject node = new JSONObject();
        node.put("id", id);
        node.put("type", type);
        node.put("data", data);
        return node;
    }

    private static class WorkflowVariant {
        private String draftContent;
        private String publishedContent;
    }
}
