package tech.easyflow.ai.easyagentsflow.service;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import tech.easyflow.common.constant.enums.EnumFieldType;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.datacenter.entity.DatacenterTable;
import tech.easyflow.datacenter.entity.DatacenterTableField;
import tech.easyflow.datacenter.execution.model.DatacenterSchemaResponse;
import tech.easyflow.datacenter.execution.model.DatasetRef;
import tech.easyflow.datacenter.execution.service.DatacenterDatasetQueryService;
import tech.easyflow.datacenter.meta.entity.DatacenterCatalog;
import tech.easyflow.datacenter.meta.entity.DatacenterSource;
import tech.easyflow.datacenter.meta.service.DatacenterDatasetRegistryService;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Service
public class WorkflowDatacenterContentService {

    public static final String SEARCH_NODE_TYPE = "search-dataset-node";
    public static final String SAVE_NODE_TYPE = "save-dataset-node";
    public static final String LLM_NODE_TYPE = "llmNode";
    public static final String QUERY_DATA_CONTEXT = "queryDataContext";
    public static final String SEARCH_SOURCE_MISSING_MESSAGE = "查询数据节点未选择连接服务";
    public static final String SEARCH_SQL_MISSING_MESSAGE = "查询数据节点未设置 SQL";
    public static final String SAVE_EXPIRED_MESSAGE = "写入数据节点配置已过期，请重新选择已接入表";
    public static final String INVALID_QUERY_CONTEXT_MESSAGE = "查询上下文配置无效，请重新选择查询数据节点";
    private static final String QUERY_CONTEXT_PROMPT = """
            你是为工作流中的查询数据节点生成只读 SQL 的生成器，你的职责是返回可直接执行的 SQL，并且你只能输出 SQL。

            必须严格遵守以下规则：
            1. 只能从下面给出的连接摘要中选择最合适的表。
            2. 只能使用摘要中给出的字段名，不要虚构表名或字段名。
            3. 只输出 SQL，不要输出解释、注释、Markdown、JSON 或多余文本。
            4. 只能生成只读 SELECT SQL，允许 WITH、JOIN、子查询、聚合、分组、排序。
            5. 不要生成 INSERT、UPDATE、DELETE、DDL、多语句、存储过程调用。
            6. 优先使用逻辑表名和逻辑字段名，不要输出物理表名、JDBC、驱动信息。
            7. 如果存在重名表，请使用 catalog.table 形式消除歧义。

            以下是可用的连接摘要：
            """;

    @Resource
    private DatacenterDatasetQueryService queryService;
    @Resource
    private DatacenterDatasetRegistryService registryService;

    public boolean isSearchDatasetNode(String nodeType) {
        return SEARCH_NODE_TYPE.equals(nodeType);
    }

    public boolean isSaveDatasetNode(String nodeType) {
        return SAVE_NODE_TYPE.equals(nodeType);
    }

    public boolean isLlmNode(String nodeType) {
        return LLM_NODE_TYPE.equals(nodeType);
    }

    public String prepareContent(String content) {
        if (!StringUtils.hasText(content)) {
            throw new BusinessException("工作流内容不能为空");
        }
        Object parsed;
        try {
            parsed = JSON.parse(content);
        } catch (Exception e) {
            throw new BusinessException("工作流内容不是合法JSON: " + shortError(e));
        }
        if (!(parsed instanceof JSONObject root)) {
            throw new BusinessException("工作流内容必须是JSON对象");
        }
        JSONObject copy = JSON.parseObject(root.toJSONString());
        prepareRoot(copy);
        return copy.toJSONString();
    }

    public JSONObject prepareRoot(JSONObject root) {
        if (root == null) {
            throw new BusinessException("工作流内容不能为空");
        }
        JSONArray nodes = root.getJSONArray("nodes");
        if (nodes == null || nodes.isEmpty()) {
            return root;
        }
        Map<String, JSONObject> nodeMap = buildNodeMap(nodes);
        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 (isSearchDatasetNode(nodeType)) {
                requireSearchDatasetRef(data);
            } else if (isSaveDatasetNode(nodeType)) {
                requireSaveDatasetRef(data);
            }
        }
        for (int i = 0; i < nodes.size(); i++) {
            JSONObject node = nodes.getJSONObject(i);
            if (node == null || !isLlmNode(node.getString("type"))) {
                continue;
            }
            injectQueryDataContext(node.getJSONObject("data"), nodeMap);
        }
        return root;
    }

    public boolean hasSaveLegacyFields(JSONObject data) {
        return hasAnyField(data, "tableId", "sourceId", "catalogId", "versionId");
    }

    public DatasetRef readDatasetRef(JSONObject data) {
        return data == null ? null : data.getObject("datasetRef", DatasetRef.class);
    }

    public DatasetRef requireSearchDatasetRef(JSONObject data) {
        DatasetRef datasetRef = readDatasetRef(data);
        if (datasetRef == null || datasetRef.getSourceId() == null) {
            throw new BusinessException(SEARCH_SOURCE_MISSING_MESSAGE);
        }
        String querySql = data == null ? null : trimToNull(data.getString("querySql"));
        if (!StringUtils.hasText(querySql)) {
            throw new BusinessException(SEARCH_SQL_MISSING_MESSAGE);
        }
        return datasetRef;
    }

    public DatasetRef requireSaveDatasetRef(JSONObject data) {
        if (hasSaveLegacyFields(data)) {
            throw new BusinessException(SAVE_EXPIRED_MESSAGE);
        }
        DatasetRef datasetRef = readDatasetRef(data);
        if (datasetRef == null || datasetRef.getTableId() == null) {
            throw new BusinessException(SAVE_EXPIRED_MESSAGE);
        }
        return datasetRef;
    }

    public DatacenterSchemaResponse loadSchema(DatasetRef datasetRef, Map<BigInteger, DatacenterSchemaResponse> schemaCache) {
        if (datasetRef == null || datasetRef.getTableId() == null) {
            throw new BusinessException("缺少已接入表配置");
        }
        BigInteger tableId = datasetRef.getTableId();
        if (schemaCache != null && schemaCache.containsKey(tableId)) {
            return schemaCache.get(tableId);
        }
        DatacenterSchemaResponse schema = queryService.getSchema(copyDatasetRef(datasetRef));
        if (schemaCache != null) {
            schemaCache.put(tableId, schema);
        }
        return schema;
    }

    public JSONObject buildSourceSummary(DatasetRef datasetRef) {
        if (datasetRef == null || datasetRef.getSourceId() == null) {
            throw new BusinessException(SEARCH_SOURCE_MISSING_MESSAGE);
        }
        DatacenterSource source = registryService.getSourceRequired(datasetRef.getSourceId());
        List<DatacenterTable> managedTables = registryService.listManagedTables(datasetRef.getSourceId(), datasetRef.getCatalogId());
        managedTables.sort(Comparator.comparing(table -> table.getTableName() == null ? "" : table.getTableName()));
        JSONObject sourceSummary = new JSONObject();
        sourceSummary.put("sourceName", source.getSourceName());
        sourceSummary.put("sourceType", source.getSourceType());
        JSONArray tables = new JSONArray();
        for (DatacenterTable table : managedTables) {
            DatacenterTable fullTable = registryService.getTableWithFields(table.getId());
            DatacenterCatalog catalog = registryService.getCatalogById(fullTable.getCatalogId());
            if (StringUtils.hasText(datasetRef.getCatalogName())
                    && (catalog == null || !datasetRef.getCatalogName().equals(catalog.getCatalogName()))) {
                continue;
            }
            JSONObject tableSummary = new JSONObject();
            tableSummary.put("catalogName", catalog == null ? null : catalog.getCatalogName());
            tableSummary.put("tableName", fullTable.getTableName());
            tableSummary.put("tableDesc", fullTable.getTableDesc());
            JSONArray fields = new JSONArray();
            if (fullTable.getFields() != null) {
                for (DatacenterTableField field : fullTable.getFields()) {
                    JSONObject fieldSummary = new JSONObject();
                    fieldSummary.put("fieldName", field.getFieldName());
                    fieldSummary.put("fieldDesc", field.getFieldDesc());
                    fieldSummary.put("fieldType", resolveFieldType(field));
                    fields.add(fieldSummary);
                }
            }
            tableSummary.put("fields", fields);
            tables.add(tableSummary);
        }
        sourceSummary.put("tables", tables);
        return sourceSummary;
    }

    private void injectQueryDataContext(JSONObject data, Map<String, JSONObject> nodeMap) {
        if (data == null) {
            return;
        }
        JSONArray nodeIds = data.getJSONArray("queryContextNodeIds");
        if (nodeIds == null || nodeIds.isEmpty()) {
            removeQueryDataContextParameter(data);
            return;
        }
        Map<BigInteger, JSONObject> sourceSummaries = new LinkedHashMap<>();
        Set<String> visitedNodeIds = new LinkedHashSet<>();
        for (int i = 0; i < nodeIds.size(); i++) {
            String nodeId = trimToNull(nodeIds.getString(i));
            if (!StringUtils.hasText(nodeId) || !visitedNodeIds.add(nodeId)) {
                continue;
            }
            JSONObject targetNode = nodeMap.get(nodeId);
            if (targetNode == null || !isSearchDatasetNode(targetNode.getString("type"))) {
                throw new BusinessException(INVALID_QUERY_CONTEXT_MESSAGE);
            }
            DatasetRef datasetRef = requireSearchDatasetRef(targetNode.getJSONObject("data"));
            sourceSummaries.putIfAbsent(datasetRef.getSourceId(), buildSourceSummary(datasetRef));
        }
        String contextValue = QUERY_CONTEXT_PROMPT + "\n" + JSON.toJSONString(new ArrayList<>(sourceSummaries.values()));
        upsertQueryDataContextParameter(data, contextValue);
    }

    private String resolveFieldType(DatacenterTableField field) {
        if (field == null) {
            return null;
        }
        if (StringUtils.hasText(field.getJdbcType())) {
            return field.getJdbcType();
        }
        try {
            EnumFieldType enumFieldType = EnumFieldType.getByCode(field.getFieldType());
            if (enumFieldType != null) {
                return enumFieldType.getText();
            }
        } catch (Exception ignored) {
        }
        return field.getFieldType() == null ? null : String.valueOf(field.getFieldType());
    }

    private void upsertQueryDataContextParameter(JSONObject data, String contextValue) {
        JSONArray parameters = data.getJSONArray("parameters");
        if (parameters == null) {
            parameters = new JSONArray();
            data.put("parameters", parameters);
        }
        for (int i = parameters.size() - 1; i >= 0; i--) {
            JSONObject parameter = parameters.getJSONObject(i);
            if (parameter != null && QUERY_DATA_CONTEXT.equals(parameter.getString("name"))) {
                parameters.remove(i);
            }
        }
        JSONObject parameter = new JSONObject();
        parameter.put("id", QUERY_DATA_CONTEXT);
        parameter.put("name", QUERY_DATA_CONTEXT);
        parameter.put("title", "查询上下文");
        parameter.put("description", "数据查询规则与连接表摘要");
        parameter.put("dataType", "String");
        parameter.put("refType", "fixed");
        parameter.put("value", contextValue);
        parameter.put("required", false);
        parameter.put("nameDisabled", true);
        parameter.put("dataTypeDisabled", true);
        parameter.put("deleteDisabled", true);
        parameters.add(parameter);
    }

    private void removeQueryDataContextParameter(JSONObject data) {
        if (data == null) {
            return;
        }
        JSONArray parameters = data.getJSONArray("parameters");
        if (parameters == null || parameters.isEmpty()) {
            return;
        }
        for (int i = parameters.size() - 1; i >= 0; i--) {
            JSONObject parameter = parameters.getJSONObject(i);
            if (parameter != null && QUERY_DATA_CONTEXT.equals(parameter.getString("name"))) {
                parameters.remove(i);
            }
        }
    }

    private Map<String, JSONObject> buildNodeMap(JSONArray nodes) {
        Map<String, JSONObject> nodeMap = new LinkedHashMap<>();
        for (int i = 0; i < nodes.size(); i++) {
            JSONObject node = nodes.getJSONObject(i);
            if (node == null) {
                continue;
            }
            String nodeId = trimToNull(node.getString("id"));
            if (StringUtils.hasText(nodeId)) {
                nodeMap.put(nodeId, node);
            }
        }
        return nodeMap;
    }

    private DatasetRef copyDatasetRef(DatasetRef datasetRef) {
        DatasetRef copy = new DatasetRef();
        copy.setSourceId(datasetRef.getSourceId());
        copy.setCatalogId(datasetRef.getCatalogId());
        copy.setCatalogName(datasetRef.getCatalogName());
        copy.setTableId(datasetRef.getTableId());
        copy.setTableName(datasetRef.getTableName());
        copy.setVersionId(datasetRef.getVersionId());
        return copy;
    }

    private boolean hasAnyField(JSONObject data, String... fieldNames) {
        if (data == null || fieldNames == null) {
            return false;
        }
        for (String fieldName : fieldNames) {
            if (data.containsKey(fieldName)) {
                return true;
            }
        }
        return false;
    }

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

    private String shortError(Throwable throwable) {
        if (throwable == null) {
            return "unknown";
        }
        String message = throwable.getMessage();
        if (!StringUtils.hasText(message)) {
            return throwable.getClass().getSimpleName();
        }
        return message.length() > 120 ? message.substring(0, 120) + "..." : message;
    }
}
