package tech.easyflow.ai.easyagentsflow.service;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.easyagents.flow.core.chain.ChainDefinition;
import com.easyagents.flow.core.chain.Parameter;
import com.easyagents.flow.core.parser.ChainParser;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.ai.entity.Workflow;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 工作流运行参数解析器。
 *
 * <p>负责统一构建运行页所需的开始节点参数、开场表单元信息和字段 Schema，
 * 避免 Admin / Public / UserCenter 三端各自维护一套解析逻辑。</p>
 */
@Service
public class WorkflowRunningParameterResolver {

    private static final String START_NODE_TYPE = "startNode";
    private static final String SYSTEM_START_PARAM_NAME = "user_input";
    private static final String DEFAULT_START_FORM_TITLE = "开始问答";
    private static final String DEFAULT_START_FORM_DESCRIPTION = "请先补充必要信息，再开始执行工作流。";
    private static final String DEFAULT_START_FORM_SUBMIT_TEXT = "开始";
    private static final int FILE_MAX_COUNT = 10;
    private static final long FILE_MAX_SINGLE_SIZE = 5L * 1024 * 1024;
    private static final long FILE_MAX_TOTAL_SIZE = 50L * 1024 * 1024;

    @Resource
    private ChainParser chainParser;
    @Resource
    private WorkflowDatacenterContentService workflowDatacenterContentService;

    /**
     * 构建工作流运行参数视图。
     *
     * @param workflow 工作流实体
     * @return 运行参数视图；当流程内容无法解析时返回 {@code null}
     */
    public Map<String, Object> buildRunningParametersView(Workflow workflow) {
        if (workflow == null) {
            return null;
        }
        List<Parameter> startParameters = resolveStartParameters(workflow.getContent());
        if (startParameters == null) {
            return null;
        }

        JSONObject startNodeData = findStartNodeData(workflow.getContent());
        List<Map<String, Object>> startFormSchema = resolveStartFormSchema(startNodeData, startParameters);
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("parameters", startParameters);
        result.put("title", workflow.getTitle());
        result.put("description", workflow.getDescription());
        result.put("icon", workflow.getIcon());
        result.put("startFormMeta", resolveStartFormMeta(startNodeData));
        result.put("startFormSchema", startFormSchema);
        return result;
    }

    /**
     * 解析开始节点运行参数。
     *
     * @param content 工作流内容
     * @return 开始节点参数；解析失败时返回 {@code null}
     */
    public List<Parameter> resolveStartParameters(String content) {
        try {
            String preparedContent = workflowDatacenterContentService.prepareContent(content);
            ChainDefinition definition = chainParser.parse(preparedContent);
            return definition == null ? null : definition.getStartParameters();
        } catch (Exception ignored) {
            return null;
        }
    }

    /**
     * 归一化工作流运行时变量，确保文件参数统一为文件对象数组。
     *
     * @param content 工作流内容
     * @param variables 原始运行变量
     * @return 归一化后的变量副本
     */
    public Map<String, Object> normalizeRuntimeVariables(String content, Map<String, Object> variables) {
        Map<String, Object> normalized = new LinkedHashMap<>();
        if (variables != null) {
            normalized.putAll(variables);
        }
        List<Parameter> startParameters = resolveStartParameters(content);
        if (startParameters == null || startParameters.isEmpty()) {
            return normalized;
        }
        for (Parameter parameter : startParameters) {
            if (!isFileParameter(parameter)) {
                continue;
            }
            String name = trimToNull(parameter.getName());
            if (!StringUtils.hasText(name) || !normalized.containsKey(name)) {
                continue;
            }
            normalized.put(name, normalizeFileVariableValue(normalized.get(name), name));
        }
        return normalized;
    }

    private Map<String, Object> resolveStartFormMeta(JSONObject startNodeData) {
        JSONObject rawMeta = startNodeData == null ? null : startNodeData.getJSONObject("startFormMeta");
        Map<String, Object> meta = new LinkedHashMap<>();
        // Missing values use defaults; an explicit empty string means the user cleared the field.
        meta.put("title", resolveOptionalStartFormText(rawMeta, "title", DEFAULT_START_FORM_TITLE));
        meta.put("description", resolveOptionalStartFormText(rawMeta, "description", DEFAULT_START_FORM_DESCRIPTION));
        meta.put("submitText", trimToDefault(rawMeta == null ? null : rawMeta.getString("submitText"), DEFAULT_START_FORM_SUBMIT_TEXT));
        return meta;
    }

    /**
     * 解析允许用户主动清空的开始表单文案。
     *
     * @param meta 开始节点表单元数据
     * @param key 文案字段名
     * @param fallback 字段缺失时的默认文案
     * @return 配置文案；字段缺失时返回默认文案，显式空字符串保持为空
     */
    private String resolveOptionalStartFormText(JSONObject meta, String key, String fallback) {
        if (meta == null || !meta.containsKey(key)) {
            return fallback;
        }
        String value = meta.getString(key);
        return value == null ? "" : value.trim();
    }

    private List<Map<String, Object>> resolveStartFormSchema(JSONObject startNodeData, List<Parameter> parameters) {
        JSONArray rawSchema = startNodeData == null ? null : startNodeData.getJSONArray("startFormSchema");
        List<Map<String, Object>> schema = new ArrayList<>();
        Set<String> seenKeys = new LinkedHashSet<>();
        boolean hasExplicitSchema = rawSchema != null;
        boolean hasSystemParameter = parameters.stream().anyMatch(parameter ->
                SYSTEM_START_PARAM_NAME.equals(trimToNull(parameter == null ? null : parameter.getName()))
        );
        if (rawSchema != null && !rawSchema.isEmpty()) {
            for (int i = 0; i < rawSchema.size(); i++) {
                JSONObject field = rawSchema.getJSONObject(i);
                Map<String, Object> normalized = normalizeStartFormField(field, null);
                if (normalized == null) {
                    continue;
                }
                String key = String.valueOf(normalized.get("key"));
                if (!seenKeys.add(key)) {
                    continue;
                }
                if (SYSTEM_START_PARAM_NAME.equals(key)) {
                    schema.add(0, normalized);
                } else {
                    schema.add(normalized);
                }
            }
        }
        if (schema.isEmpty()) {
            for (Parameter parameter : parameters) {
                Map<String, Object> normalized = normalizeStartFormField(null, parameter);
                if (normalized == null) {
                    continue;
                }
                String key = String.valueOf(normalized.get("key"));
                if (!seenKeys.add(key)) {
                    continue;
                }
                if (SYSTEM_START_PARAM_NAME.equals(key)) {
                    schema.add(0, normalized);
                } else {
                    schema.add(normalized);
                }
            }
        }
        if (!seenKeys.contains(SYSTEM_START_PARAM_NAME) && (hasExplicitSchema || hasSystemParameter)) {
            schema.add(0, normalizeStartFormField(new JSONObject(), null));
        }
        return schema;
    }

    private Map<String, Object> normalizeStartFormField(JSONObject field, Parameter parameter) {
        String parameterName = parameter == null ? null : trimToNull(parameter.getName());
        String key = trimToNull(field == null ? null : field.getString("key"));
        if (!StringUtils.hasText(key)) {
            key = parameterName;
        }
        if (!StringUtils.hasText(key)) {
            key = SYSTEM_START_PARAM_NAME;
        }
        boolean systemReserved = SYSTEM_START_PARAM_NAME.equals(key)
                || (field != null && Boolean.TRUE.equals(field.getBoolean("systemReserved")));
        String type = resolveStartFormFieldType(field == null ? null : field.getString("type"), parameter, systemReserved);
        List<String> options = resolveFieldOptions(field, parameter, type);

        Map<String, Object> normalized = new LinkedHashMap<>();
        normalized.put("key", key);
        normalized.put("label", trimToDefault(
                field == null ? null : field.getString("label"),
                parameter == null ? null : parameter.getFormLabel(),
                SYSTEM_START_PARAM_NAME.equals(key) ? "用户问题" : key
        ));
        normalized.put("type", type);
        normalized.put("required", systemReserved || (field != null && Boolean.TRUE.equals(field.getBoolean("required")))
                || (parameter != null && parameter.isRequired()));
        normalized.put("placeholder", trimToDefault(
                field == null ? null : field.getString("placeholder"),
                parameter == null ? null : parameter.getFormPlaceholder(),
                SYSTEM_START_PARAM_NAME.equals(key) ? "请输入用户问题" : ""
        ));
        normalized.put("description", trimToDefault(
                field == null ? null : field.getString("description"),
                parameter == null ? null : parameter.getFormDescription(),
                ""
        ));
        normalized.put("defaultValue", resolveDefaultValue(field, parameter, type));
        normalized.put("options", options);
        normalized.put("systemReserved", systemReserved);
        return normalized;
    }

    private Object resolveDefaultValue(JSONObject field, Parameter parameter, String type) {
        Object rawDefaultValue = field == null ? null : field.get("defaultValue");
        if (rawDefaultValue != null) {
            if ("checkbox".equals(type) && rawDefaultValue instanceof List<?>) {
                return rawDefaultValue;
            }
            return rawDefaultValue;
        }
        return parameter == null ? "" : parameter.getDefaultValue();
    }

    private List<String> resolveFieldOptions(JSONObject field, Parameter parameter, String type) {
        if (!"radio".equals(type) && !"checkbox".equals(type) && !"select".equals(type)) {
            return new ArrayList<>();
        }
        List<String> options = new ArrayList<>();
        if (field != null) {
            JSONArray rawOptions = field.getJSONArray("options");
            if (rawOptions != null) {
                for (int i = 0; i < rawOptions.size(); i++) {
                    String option = trimToNull(rawOptions.getString(i));
                    if (StringUtils.hasText(option)) {
                        options.add(option);
                    }
                }
            }
        }
        if (!options.isEmpty()) {
            return options;
        }
        if (parameter != null && parameter.getEnums() != null) {
            for (Object option : parameter.getEnums()) {
                String normalized = trimToNull(option == null ? null : String.valueOf(option));
                if (StringUtils.hasText(normalized)) {
                    options.add(normalized);
                }
            }
        }
        return options;
    }

    private String resolveStartFormFieldType(String rawType, Parameter parameter, boolean systemReserved) {
        String parameterType = parameterType(parameter);
        String requested = trimToNull(rawType);
        String normalized;
        if (StringUtils.hasText(requested)) {
            normalized = requested;
        } else if (StringUtils.hasText(parameterType)) {
            normalized = parameterType;
        } else {
            normalized = systemReserved ? "textarea" : "text";
        }
        if (systemReserved) {
            return "text".equals(normalized) ? "text" : "textarea";
        }
        return switch (normalized) {
            case "textarea", "radio", "checkbox", "select", "file" -> normalized;
            default -> "text";
        };
    }

    private String parameterType(Parameter parameter) {
        if (parameter == null) {
            return null;
        }
        if ("file".equals(trimToNull(parameter.getContentType()))
                || "file".equalsIgnoreCase(trimToNull(String.valueOf(parameter.getDataType())))) {
            return "file";
        }
        String formType = trimToNull(parameter.getFormType());
        if ("textarea".equals(formType)) {
            return "textarea";
        }
        if ("radio".equals(formType)) {
            return "radio";
        }
        if ("checkbox".equals(formType)) {
            return "checkbox";
        }
        if ("select".equals(formType)) {
            return "select";
        }
        return SYSTEM_START_PARAM_NAME.equals(trimToNull(parameter.getName())) ? "textarea" : "text";
    }

    /**
     * 判断参数是否为文件输入参数。
     *
     * @param parameter 参数定义
     * @return 是否文件参数
     */
    private boolean isFileParameter(Parameter parameter) {
        if (parameter == null) {
            return false;
        }
        return "file".equals(trimToNull(parameter.getContentType()))
                || "file".equalsIgnoreCase(trimToNull(String.valueOf(parameter.getDataType())));
    }

    /**
     * 将单文件或多文件运行值归一化为文件对象数组。
     *
     * @param value 原始变量值
     * @param parameterName 参数名
     * @return 归一化后的文件对象数组
     */
    private List<Object> normalizeFileVariableValue(Object value, String parameterName) {
        List<Object> candidates = new ArrayList<>();
        collectFileValues(value, candidates);
        if (candidates.isEmpty()) {
            return new ArrayList<>();
        }

        List<Object> normalized = new ArrayList<>();
        Set<String> seenFilePaths = new LinkedHashSet<>();
        long totalSize = 0L;
        for (Object candidate : candidates) {
            if (!(candidate instanceof Map<?, ?> fileMap)) {
                throw new BusinessException("文件参数 " + parameterName + " 的输入格式不正确，必须为文件对象或文件对象数组");
            }
            String fileName = trimObjectToNull(fileMap.get("fileName"));
            String filePath = trimObjectToNull(fileMap.get("filePath"));
            if (!StringUtils.hasText(fileName)) {
                throw new BusinessException("文件参数 " + parameterName + " 缺少 fileName");
            }
            if (!StringUtils.hasText(filePath)) {
                throw new BusinessException("文件参数 " + parameterName + " 缺少 filePath");
            }
            if (!seenFilePaths.add(filePath)) {
                continue;
            }
            Long size = parseLong(fileMap.get("size"));
            if (size != null && size > FILE_MAX_SINGLE_SIZE) {
                throw new BusinessException("文件参数 " + parameterName + " 中单个文件不能超过 5MB");
            }
            if (size != null && size > 0) {
                totalSize += size;
            }
            normalized.add(new LinkedHashMap<>(fileMap));
        }

        if (normalized.size() > FILE_MAX_COUNT) {
            throw new BusinessException("文件参数 " + parameterName + " 最多上传 10 个文件");
        }
        if (totalSize > FILE_MAX_TOTAL_SIZE) {
            throw new BusinessException("文件参数 " + parameterName + " 的文件总大小不能超过 50MB");
        }
        return normalized;
    }

    private void collectFileValues(Object value, List<Object> result) {
        if (value == null) {
            return;
        }
        if (value instanceof Collection<?> collection) {
            for (Object item : collection) {
                collectFileValues(item, result);
            }
            return;
        }
        result.add(value);
    }

    private String trimObjectToNull(Object value) {
        return trimToNull(value == null ? null : String.valueOf(value));
    }

    private Long parseLong(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Number number) {
            return number.longValue();
        }
        String text = trimObjectToNull(value);
        if (!StringUtils.hasText(text)) {
            return null;
        }
        try {
            return Long.parseLong(text);
        } catch (NumberFormatException e) {
            throw new BusinessException("文件大小格式不正确: " + text);
        }
    }

    private JSONObject findStartNodeData(String content) {
        if (!StringUtils.hasText(content)) {
            return null;
        }
        try {
            Object parsed = JSON.parse(content);
            if (!(parsed instanceof JSONObject root)) {
                return null;
            }
            JSONArray nodes = root.getJSONArray("nodes");
            if (nodes == null) {
                return null;
            }
            for (int i = 0; i < nodes.size(); i++) {
                JSONObject node = nodes.getJSONObject(i);
                if (node == null) {
                    continue;
                }
                if (!START_NODE_TYPE.equals(trimToNull(node.getString("type")))) {
                    continue;
                }
                return node.getJSONObject("data");
            }
            return null;
        } catch (Exception ignored) {
            return null;
        }
    }

    private String trimToDefault(String value, String fallback) {
        String normalized = trimToNull(value);
        return StringUtils.hasText(normalized) ? normalized : fallback;
    }

    private String trimToDefault(String primary, String secondary, String fallback) {
        String normalizedPrimary = trimToNull(primary);
        if (StringUtils.hasText(normalizedPrimary)) {
            return normalizedPrimary;
        }
        String normalizedSecondary = trimToNull(secondary);
        return StringUtils.hasText(normalizedSecondary) ? normalizedSecondary : fallback;
    }

    private String trimToNull(String value) {
        if (!StringUtils.hasText(value)) {
            return null;
        }
        return value.trim();
    }
}
