package tech.easyflow.ai.easyagentsflow.service;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.easyagents.flow.core.parser.ChainParser;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.node.SearchDatasetNodeParser;
import tech.easyflow.ai.node.WorkflowNodeParser;

import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * {@link WorkflowRunningParameterResolver} 测试。
 */
public class WorkflowRunningParameterResolverTest {

    /**
     * 应返回显式配置的开场表单元信息与字段 Schema。
     *
     * @throws Exception 反射注入失败
     */
    @Test
    public void testBuildRunningParametersViewShouldKeepStartFormSchema() throws Exception {
        WorkflowRunningParameterResolver resolver = newResolver();
        JSONObject startData = data("开始");
        JSONArray schema = new JSONArray();

        JSONObject userInputField = new JSONObject();
        userInputField.put("key", "user_input");
        userInputField.put("label", "主问题");
        userInputField.put("type", "text");
        userInputField.put("required", true);
        schema.add(userInputField);

        JSONObject fileField = new JSONObject();
        fileField.put("key", "attachments");
        fileField.put("label", "附件");
        fileField.put("type", "file");
        fileField.put("required", false);
        schema.add(fileField);

        JSONObject meta = new JSONObject();
        meta.put("title", "问答入口");
        meta.put("description", "请先填写信息");
        meta.put("submitText", "立即开始");
        startData.put("startFormMeta", meta);
        startData.put("startFormSchema", schema);
        startData.put("parameters", startParameters());

        Workflow workflow = workflow(
                workflowJson(
                        array(
                                node("s1", "startNode", null, startData),
                                node("e1", "endNode", null, data("结束"))
                        ),
                        array(edge("e1", "s1", "e1"))
                )
        );

        Map<String, Object> result = resolver.buildRunningParametersView(workflow);
        Assert.assertNotNull(result);
        Assert.assertEquals("问答入口", ((Map<?, ?>) result.get("startFormMeta")).get("title"));
        List<Map<String, Object>> fields = (List<Map<String, Object>>) result.get("startFormSchema");
        Assert.assertEquals(2, fields.size());
        Assert.assertEquals("user_input", fields.get(0).get("key"));
        Assert.assertEquals("text", fields.get(0).get("type"));
        Assert.assertEquals("attachments", fields.get(1).get("key"));
        Assert.assertEquals("file", fields.get(1).get("type"));
    }

    /**
     * 旧工作流仅保留 parameters 时，也应补出最小开场表单 Schema。
     *
     * @throws Exception 反射注入失败
     */
    @Test
    public void testBuildRunningParametersViewShouldFallbackToParameters() throws Exception {
        WorkflowRunningParameterResolver resolver = newResolver();
        JSONObject startData = data("开始");
        startData.put("parameters", startParameters());

        Workflow workflow = workflow(
                workflowJson(
                        array(
                                node("s1", "startNode", null, startData),
                                node("e1", "endNode", null, data("结束"))
                        ),
                        array(edge("e1", "s1", "e1"))
                )
        );

        Map<String, Object> result = resolver.buildRunningParametersView(workflow);
        Assert.assertNotNull(result);
        List<Map<String, Object>> fields = (List<Map<String, Object>>) result.get("startFormSchema");
        Assert.assertEquals(2, fields.size());
        Assert.assertEquals("user_input", fields.get(0).get("key"));
        Assert.assertEquals("textarea", fields.get(0).get("type"));
        Assert.assertEquals(Boolean.TRUE, fields.get(0).get("required"));
        Assert.assertEquals("attachments", fields.get(1).get("key"));
        Assert.assertEquals("file", fields.get(1).get("type"));
    }

    /**
     * 旧工作流若不存在系统问题参数，运行态不应凭空注入 user_input。
     *
     * @throws Exception 反射注入失败
     */
    @Test
    public void testBuildRunningParametersViewShouldNotInjectUserInputForLegacyParametersWithoutSystemField() throws Exception {
        WorkflowRunningParameterResolver resolver = newResolver();
        JSONObject startData = data("开始");
        startData.put("parameters", startParametersWithoutUserInput());

        Workflow workflow = workflow(
                workflowJson(
                        array(
                                node("s1", "startNode", null, startData),
                                node("e1", "endNode", null, data("结束"))
                        ),
                        array(edge("e1", "s1", "e1"))
                )
        );

        Map<String, Object> result = resolver.buildRunningParametersView(workflow);
        Assert.assertNotNull(result);
        List<Map<String, Object>> fields = (List<Map<String, Object>>) result.get("startFormSchema");
        Assert.assertEquals(1, fields.size());
        Assert.assertEquals("attachments", fields.get(0).get("key"));
        Assert.assertEquals("file", fields.get(0).get("type"));
    }

    /**
     * 文件参数运行值应统一归一化为数组并按 filePath 去重。
     *
     * @throws Exception 反射注入失败
     */
    @Test
    public void testNormalizeRuntimeVariablesShouldWrapFileValueIntoArray() throws Exception {
        WorkflowRunningParameterResolver resolver = newResolver();
        Map<String, Object> variables = new LinkedHashMap<>();
        variables.put("attachments", fileValue("demo.pdf", "/files/demo.pdf", 1024L));

        Map<String, Object> normalized = resolver.normalizeRuntimeVariables(workflowContentWithStartParameters(), variables);

        Object attachments = normalized.get("attachments");
        Assert.assertTrue(attachments instanceof List<?>);
        Assert.assertEquals(1, ((List<?>) attachments).size());
        Assert.assertTrue(((List<?>) attachments).get(0) instanceof Map<?, ?>);
    }

    /**
     * 多文件参数应按 filePath 去重并保留已有非文件变量。
     *
     * @throws Exception 反射注入失败
     */
    @Test
    public void testNormalizeRuntimeVariablesShouldDedupeFileArray() throws Exception {
        WorkflowRunningParameterResolver resolver = newResolver();
        Map<String, Object> variables = new LinkedHashMap<>();
        variables.put("user_input", "hello");
        variables.put("attachments", List.of(
                fileValue("a.pdf", "/files/a.pdf", 1024L),
                fileValue("a-copy.pdf", "/files/a.pdf", 1024L),
                fileValue("b.pdf", "/files/b.pdf", 1024L)
        ));

        Map<String, Object> normalized = resolver.normalizeRuntimeVariables(workflowContentWithStartParameters(), variables);

        Assert.assertEquals("hello", normalized.get("user_input"));
        Object attachments = normalized.get("attachments");
        Assert.assertTrue(attachments instanceof List<?>);
        Assert.assertEquals(2, ((List<?>) attachments).size());
    }

    private static WorkflowRunningParameterResolver newResolver() throws Exception {
        WorkflowRunningParameterResolver resolver = new WorkflowRunningParameterResolver();
        ChainParser parser = ChainParser.builder()
                .withDefaultParsers(true)
                .build();
        parser.addNodeParser("workflow-node", new WorkflowNodeParser());
        parser.addNodeParser("search-dataset-node", new SearchDatasetNodeParser());
        setField(resolver, "chainParser", parser);
        setField(resolver, "workflowDatacenterContentService", new WorkflowDatacenterContentService());
        return resolver;
    }

    private static Workflow workflow(String content) {
        Workflow workflow = new Workflow();
        workflow.setId(BigInteger.ONE);
        workflow.setTitle("测试工作流");
        workflow.setDescription("用于测试运行参数");
        workflow.setIcon("icon.png");
        workflow.setContent(content);
        return workflow;
    }

    private static String workflowContentWithStartParameters() {
        JSONObject startData = data("开始");
        startData.put("parameters", startParameters());
        return workflowJson(
                array(
                        node("s1", "startNode", null, startData),
                        node("e1", "endNode", null, data("结束"))
                ),
                array(edge("e1", "s1", "e1"))
        );
    }

    private static JSONArray startParameters() {
        JSONArray parameters = new JSONArray();

        JSONObject userInput = new JSONObject();
        userInput.put("name", "user_input");
        userInput.put("dataType", "String");
        userInput.put("refType", "input");
        userInput.put("required", true);
        userInput.put("contentType", "text");
        userInput.put("formType", "textarea");
        userInput.put("formLabel", "用户问题");
        userInput.put("formPlaceholder", "请输入用户问题");
        parameters.add(userInput);

        JSONObject fileField = new JSONObject();
        fileField.put("name", "attachments");
        fileField.put("dataType", "File");
        fileField.put("refType", "input");
        fileField.put("required", false);
        fileField.put("contentType", "file");
        fileField.put("formType", "input");
        fileField.put("formLabel", "附件");
        parameters.add(fileField);
        return parameters;
    }

    private static JSONArray startParametersWithoutUserInput() {
        JSONArray parameters = new JSONArray();

        JSONObject fileField = new JSONObject();
        fileField.put("name", "attachments");
        fileField.put("dataType", "File");
        fileField.put("refType", "input");
        fileField.put("required", false);
        fileField.put("contentType", "file");
        fileField.put("formType", "input");
        fileField.put("formLabel", "附件");
        parameters.add(fileField);
        return parameters;
    }

    private static Map<String, Object> fileValue(String fileName, String filePath, Long size) {
        Map<String, Object> value = new LinkedHashMap<>();
        value.put("fileName", fileName);
        value.put("filePath", filePath);
        value.put("size", size);
        value.put("contentType", "application/pdf");
        value.put("url", filePath);
        return value;
    }

    private static void setField(Object target, String fieldName, Object value) throws Exception {
        Field field = WorkflowRunningParameterResolver.class.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(target, value);
    }

    private static String workflowJson(JSONArray nodes, JSONArray edges) {
        JSONObject root = new JSONObject();
        root.put("nodes", nodes);
        root.put("edges", edges);
        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 node(String id, String type, String parentId, JSONObject data) {
        JSONObject node = new JSONObject();
        node.put("id", id);
        node.put("type", type);
        if (parentId != null) {
            node.put("parentId", parentId);
        }
        node.put("data", data);
        return node;
    }

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

    private static JSONObject edge(String id, String source, String target) {
        JSONObject edge = new JSONObject();
        edge.put("id", id);
        edge.put("source", source);
        edge.put("target", target);
        return edge;
    }
}
