/**
 * Copyright (c) 2025-2026, Michael Yang 杨福海 (fuhai999@gmail.com).
 * <p>
 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/lgpl-3.0.txt
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.easyagents.flow.core.chain;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.easyagents.flow.core.util.MapUtil;
import com.easyagents.flow.core.util.StringUtil;
import com.easyagents.flow.core.util.TextTemplate;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class ChainState implements Serializable {

    private static final long serialVersionUID = -7958235553581638052L;

    private String instanceId;
    private String parentInstanceId;
    /**
     * 节点审计应归属的顶级执行实例 ID。
     */
    private String auditInstanceId;
    private String chainDefinitionId;
    private ConcurrentHashMap<String, Object> memory = new ConcurrentHashMap<>();

    private Map<String, Object> executeResult;
    private Map<String, Object> environment;

    private List<String> triggerEdgeIds;
    private List<String> triggerNodeIds;

    private List<String> uncheckedEdgeIds;
    private List<String> uncheckedNodeIds;

    // 算力消耗定义，积分消耗
    private long computeCost;
    private Set<String> suspendNodeIds;
    private List<Parameter> suspendForParameters;
    private ChainStatus status;
    private String message;
    private ExceptionSummary error;
    private long version;
    /**
     * 工作流实例首次启动时间，用于跨线程和跨进程执行时长保护。
     */
    private long startedAt;
    /**
     * 已进入业务执行的节点次数，用于全局执行预算。
     */
    private long childExecutionCount;

    public ChainState() {
        this.instanceId = UUID.randomUUID().toString();
        this.auditInstanceId = this.instanceId;
        this.status = ChainStatus.READY;
        this.computeCost = 0;
    }

    public String getInstanceId() {
        return instanceId;
    }

    public void setInstanceId(String instanceId) {
        String previousInstanceId =
                this.instanceId;
        this.instanceId = instanceId;
        if (auditInstanceId == null
                || Objects.equals(
                auditInstanceId,
                previousInstanceId)) {
            auditInstanceId = instanceId;
        }
    }

    public String getParentInstanceId() {
        return parentInstanceId;
    }

    public void setParentInstanceId(String parentInstanceId) {
        this.parentInstanceId = parentInstanceId;
        if (StringUtil.hasText(parentInstanceId)
                && Objects.equals(
                auditInstanceId, instanceId)) {
            auditInstanceId = null;
        }
    }

    /**
     * 获取节点审计归属实例 ID。
     *
     * @return 顶级审计实例 ID
     */
    public String getAuditInstanceId() {
        return auditInstanceId;
    }

    /**
     * 设置节点审计归属实例 ID。
     *
     * @param auditInstanceId 顶级审计实例 ID
     */
    public void setAuditInstanceId(
            String auditInstanceId) {
        this.auditInstanceId = auditInstanceId;
    }

    public String getChainDefinitionId() {
        return chainDefinitionId;
    }

    public void setChainDefinitionId(String chainDefinitionId) {
        this.chainDefinitionId = chainDefinitionId;
    }

    public ConcurrentHashMap<String, Object> getMemory() {
        return memory;
    }

    public void setMemory(ConcurrentHashMap<String, Object> memory) {
        this.memory = memory;
    }

    public Map<String, Object> getExecuteResult() {
        return executeResult;
    }

    public void setExecuteResult(Map<String, Object> executeResult) {
        this.executeResult = executeResult;
    }

    public Map<String, Object> getEnvironment() {
        return environment;
    }

    public void setEnvironment(Map<String, Object> environment) {
        this.environment = environment;
    }


    public List<String> getTriggerEdgeIds() {
        return triggerEdgeIds;
    }

    public void setTriggerEdgeIds(List<String> triggerEdgeIds) {
        this.triggerEdgeIds = triggerEdgeIds;
    }

    public void addTriggerEdgeId(String edgeId) {
        if (triggerEdgeIds == null) {
            triggerEdgeIds = new ArrayList<>();
        }
        if (!triggerEdgeIds.contains(edgeId)) {
            triggerEdgeIds.add(edgeId);
        }
    }

    public List<String> getTriggerNodeIds() {
        return triggerNodeIds;
    }

    public void setTriggerNodeIds(List<String> triggerNodeIds) {
        this.triggerNodeIds = triggerNodeIds;
    }

    public void addTriggerNodeId(String nodeId) {
        if (triggerNodeIds == null) {
            triggerNodeIds = new ArrayList<>();
        }
        if (!triggerNodeIds.contains(nodeId)) {
            triggerNodeIds.add(nodeId);
        }
    }

    public List<String> getUncheckedEdgeIds() {
        return uncheckedEdgeIds;
    }

    public void setUncheckedEdgeIds(List<String> uncheckedEdgeIds) {
        this.uncheckedEdgeIds = uncheckedEdgeIds == null
                ? new ArrayList<>()
                : new ArrayList<>(new LinkedHashSet<>(uncheckedEdgeIds));
    }

    public boolean addUncheckedEdgeId(String edgeId) {
        if (uncheckedEdgeIds == null) {
            uncheckedEdgeIds = new ArrayList<>();
        }
        if (uncheckedEdgeIds.contains(edgeId)) {
            return false;
        }
        uncheckedEdgeIds.add(edgeId);
        return true;
    }

    public boolean removeUncheckedEdgeId(String edgeId) {
        if (uncheckedEdgeIds == null) {
            return false;
        }
        boolean removed = false;
        while (uncheckedEdgeIds.remove(edgeId)) {
            removed = true;
        }
        return removed;
    }

    public List<String> getUncheckedNodeIds() {
        return uncheckedNodeIds;
    }

    public void setUncheckedNodeIds(List<String> uncheckedNodeIds) {
        this.uncheckedNodeIds = uncheckedNodeIds == null
                ? new ArrayList<>()
                : new ArrayList<>(new LinkedHashSet<>(uncheckedNodeIds));
    }

    public boolean addUncheckedNodeId(String nodeId) {
        if (uncheckedNodeIds == null) {
            uncheckedNodeIds = new ArrayList<>();
        }
        if (uncheckedNodeIds.contains(nodeId)) {
            return false;
        }
        uncheckedNodeIds.add(nodeId);
        return true;
    }

    public boolean removeUncheckedNodeId(String nodeId) {
        if (uncheckedNodeIds == null) {
            return false;
        }
        boolean removed = false;
        while (uncheckedNodeIds.remove(nodeId)) {
            removed = true;
        }
        return removed;
    }

    public Long getComputeCost() {
        return computeCost;
    }

    public void setComputeCost(Long computeCost) {
        this.computeCost = computeCost;
    }

    public void setComputeCost(long computeCost) {
        this.computeCost = computeCost;
    }

    public Set<String> getSuspendNodeIds() {
        return suspendNodeIds;
    }

    public void setSuspendNodeIds(Set<String> suspendNodeIds) {
        this.suspendNodeIds = suspendNodeIds;
    }

    public void addSuspendNodeId(String nodeId) {
        if (suspendNodeIds == null) {
            suspendNodeIds = new HashSet<>();
        }
        suspendNodeIds.add(nodeId);
    }

    public void removeSuspendNodeId(String nodeId) {
        if (suspendNodeIds == null) {
            return;
        }
        suspendNodeIds.remove(nodeId);
    }

    public List<Parameter> getSuspendForParameters() {
        return suspendForParameters;
    }

    public void setSuspendForParameters(List<Parameter> suspendForParameters) {
        this.suspendForParameters = suspendForParameters;
    }

    public void addSuspendForParameter(Parameter parameter) {
        if (suspendForParameters == null) {
            suspendForParameters = new ArrayList<>();
        }
        suspendForParameters.add(parameter);
    }

    public void addSuspendForParameters(List<Parameter> parameters) {
        if (parameters == null) {
            return;
        }
        if (suspendForParameters == null) {
            suspendForParameters = new ArrayList<>();
        }
        suspendForParameters.addAll(parameters);
    }

    public ChainStatus getStatus() {
        return status;
    }

    public void setStatus(ChainStatus status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public ExceptionSummary getError() {
        return error;
    }

    public void setError(ExceptionSummary error) {
        this.error = error;
    }


    public long getVersion() {
        return version;
    }

    public void setVersion(long version) {
        this.version = version;
    }

    public long getStartedAt() {
        return startedAt;
    }

    public void setStartedAt(long startedAt) {
        this.startedAt = startedAt;
    }

    public long getChildExecutionCount() {
        return childExecutionCount;
    }

    public void setChildExecutionCount(long childExecutionCount) {
        this.childExecutionCount = childExecutionCount;
    }

    public static ChainState fromJSON(String jsonString) {
        ParserConfig config = new ParserConfig();
        config.putDeserializer(ChainState.class, new ChainDeserializer());
        return JSON.parseObject(jsonString, ChainState.class, config, Feature.SupportAutoType);
    }

    public String toJSON() {
        SerializeConfig config = new SerializeConfig();
        config.put(ChainState.class, new ChainSerializer());
        return JSON.toJSONString(this, config, SerializerFeature.WriteClassName);
    }

    public void reset() {
        this.instanceId = null;
        this.chainDefinitionId = null;
        this.memory.clear();
        this.executeResult = null;
        this.environment = null;
        this.computeCost = 0;
        this.suspendNodeIds = null;
        this.suspendForParameters = null;
        this.status = ChainStatus.READY;
        this.message = null;
        this.error = null;
        this.startedAt = 0L;
        this.childExecutionCount = 0L;
    }


    public void addComputeCost(Long value) {
        if (value == null) {
            value = 0L;
        }
        this.computeCost += value;
    }


    public Map<String, Object> getNodeExecuteResult(String nodeId) {
        if (memory == null || memory.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<String, Object> result = new HashMap<>();
        memory.forEach((k, v) -> {
            if (k.startsWith(nodeId + ".")) {
                String newKey = k.substring(nodeId.length() + 1);
                result.put(newKey, v);
            }
        });
        return result;
    }

//    public Map<String, Object> getTriggerVariables() {
//        Trigger trigger = TriggerContext.getCurrentTrigger();
//        if (trigger != null) {
//            return trigger.getVariables();
//        }
//        return Collections.emptyMap();
//    }


    public Object resolveValue(String path) {
        return resolveValue(path, false);
    }

    /**
     * 解析参数路径，并可在直接命中时保留大型结果引用。
     *
     * @param path 参数路径
     * @param preserveDirectReference 是否保留直接命中的引用
     * @return 参数值
     */
    private Object resolveValue(
            String path,
            boolean preserveDirectReference) {
        Object result = MapUtil.getByPath(getMemory(), path);
        if (result == null) result = MapUtil.getByPath(getEnvironment(), path);
//        if (result == null) result = MapUtil.getByPath(getTriggerVariables(), path);
        Chain chain = Chain.currentChain();
        if (result != null || chain == null || memory == null || path == null) {
            return chain == null
                    || preserveDirectReference
                    ? result
                    : chain.resolveResultReferences(result);
        }

        // MapUtil 无法直接穿透轻量引用，先解析最长命中的作用域值，再继续解析剩余路径。
        String[] parts = path.split("\\.");
        for (int length = parts.length - 1; length > 0; length--) {
            String prefix = String.join(".", Arrays.copyOf(parts, length));
            Object referenced = memory.get(prefix);
            if (referenced == null) {
                continue;
            }
            Object resolved = chain.resolveResultReferences(referenced);
            String remaining = String.join(
                    ".", Arrays.copyOfRange(parts, length, parts.length));
            return MapUtil.getByPath(
                    Collections.singletonMap("value", resolved),
                    "value." + remaining);
        }
        return null;
    }

    public Map<String, Object> resolveParameters(Node node) {
        return resolveParameters(node, node.getParameters());
    }

    public Map<String, Object> resolveParameters(Node node, List<? extends Parameter> parameters) {
        return resolveParameters(node, parameters, null);
    }

    public Map<String, Object> resolveParameters(Node node, List<? extends Parameter> parameters, Map<String, Object> formatArgs) {
        return resolveParameters(node, parameters, formatArgs, false);
    }

    private boolean isNullOrBlank(Object value) {
        return value == null || value instanceof String && StringUtil.noText((String) value);
    }


    public Map<String, Object> getEnvMap() {
        Map<String, Object> formatArgsMap = new HashMap<>();
        formatArgsMap.put("env", getEnvironment());
        formatArgsMap.put("env.sys", System.getenv());
        return formatArgsMap;
    }

    /**
     * 构建文本模板渲染所需的上下文列表。
     * <p>
     * 顺序为：链路 memory -> 当前节点参数/临时参数 -> 环境变量。
     * 后面的 map 会覆盖前面的同名 key，这样既能兼容直接引用历史节点输出，
     * 又能保证当前节点显式解析出的参数优先级更高。
     *
     * @param formatArgs 当前节点参与模板渲染的参数
     * @return 模板渲染上下文列表
     */
    public List<Map<String, Object>> buildTemplateRootMaps(Map<String, Object> formatArgs) {
        Chain chain = Chain.currentChain();
        Map<String, Object> runtimeMemory = getMemory();
        if (chain != null && runtimeMemory != null && !runtimeMemory.isEmpty()) {
            runtimeMemory = new LazyReferenceMap(
                    runtimeMemory, chain);
        }
        return Arrays.asList(runtimeMemory, formatArgs, getEnvMap());
    }

    /**
     * 构建审计参数使用的惰性模板上下文。
     *
     * <p>仅在模板实际读取某个 memory 顶级值时还原其中的轻量引用，
     * 避免无关固定参数同步物化大型结果。</p>
     *
     * @param formatArgs 当前节点参与模板渲染的参数
     * @return 惰性模板上下文列表
     */
    private List<Map<String, Object>>
    buildLazyTemplateRootMaps(
            Map<String, Object> formatArgs) {
        return buildTemplateRootMaps(formatArgs);
    }

    /**
     * 构建文本模板渲染所需的合并上下文。
     *
     * @param formatArgs 当前节点参与模板渲染的参数
     * @return 合并后的模板上下文
     */
    public Map<String, Object> buildTemplateContextMap(Map<String, Object> formatArgs) {
        Map<String, Object> templateContext = new LinkedHashMap<>();
        if (memory != null && !memory.isEmpty()) {
            templateContext.putAll(memory);
        }
        if (formatArgs != null && !formatArgs.isEmpty()) {
            templateContext.putAll(formatArgs);
        }
        templateContext.putAll(getEnvMap());
        return templateContext;
    }

    public Map<String, Object> resolveParameters(Node node, List<? extends Parameter> parameters, Map<String, Object> formatArgs, boolean ignoreRequired) {
        return resolveParameters(
                node,
                parameters,
                formatArgs,
                ignoreRequired,
                false);
    }

    /**
     * 解析节点审计输入，直接引用保持轻量形式，由审计消费者异步还原。
     *
     * @param node 当前节点
     * @return 兼容既有输入字段结构的参数快照
     */
    public Map<String, Object> resolveParametersPreservingReferences(
            Node node) {
        return resolveParameters(
                node,
                node.getParameters(),
                null,
                false,
                true);
    }

    /**
     * 解析节点参数。
     *
     * @param node 当前节点
     * @param parameters 参数定义
     * @param formatArgs 模板附加参数
     * @param ignoreRequired 是否忽略必填校验
     * @param preserveDirectReferences 是否保留直接结果引用
     * @return 已解析参数
     */
    private Map<String, Object> resolveParameters(
            Node node,
            List<? extends Parameter> parameters,
            Map<String, Object> formatArgs,
            boolean ignoreRequired,
            boolean preserveDirectReferences) {
        if (parameters == null || parameters.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<String, Object> variables = new LinkedHashMap<>();
        List<Parameter> suspendParameters = null;
        List<Map<String, Object>> templateRootMaps = null;
        for (Parameter parameter : parameters) {
            RefType refType = parameter.getRefType();
            Object value = null;
            if (refType == RefType.FIXED) {
                if (templateRootMaps == null) {
                    templateRootMaps =
                            preserveDirectReferences
                                    ? buildLazyTemplateRootMaps(
                                    formatArgs)
                                    : buildTemplateRootMaps(
                                    formatArgs);
                }
                value = TextTemplate.of(parameter.getValue())
                        .formatToString(templateRootMaps);
            } else if (refType == RefType.REF) {
                value = this.resolveValue(
                        parameter.getRef(),
                        preserveDirectReferences);
            }
            // 单节点执行时，参数只会传入 name 内容。
            if (value == null) {
                value = this.resolveValue(
                        parameter.getName(),
                        preserveDirectReferences);
            }

            if (value == null && parameter.getDefaultValue() != null) {
                value = parameter.getDefaultValue();
            }

            if (refType == RefType.INPUT && isNullOrBlank(value)) {
                if (!ignoreRequired && parameter.isRequired()) {
                    if (suspendParameters == null) {
                        suspendParameters = new ArrayList<>();
                    }
                    suspendParameters.add(parameter);
                    continue;
                }
            }

            if (parameter.isRequired() && isNullOrBlank(value)) {
                if (!ignoreRequired) {
                    throw new ChainException(node.getName() + " Missing required parameter:" + parameter.getName());
                }
            }

            if (value instanceof String) {
                value = ((String) value).trim();
                if (parameter.getDataType() == DataType.Boolean) {
                    value = "true".equalsIgnoreCase((String) value) || "1".equalsIgnoreCase((String) value);
                } else if (parameter.getDataType() == DataType.Number) {
                    value = Long.parseLong((String) value);
                } else if (parameter.getDataType() == DataType.Array) {
                    value = JSON.parseArray((String) value);
                }
            }

            variables.put(parameter.getName(), value);
        }

        if (suspendParameters != null && !suspendParameters.isEmpty()) {
            // 构建参数名称列表
            String missingParams = suspendParameters.stream()
                    .map(Parameter::getName)
                    .collect(Collectors.joining("', '", "'", "'"));

            String errorMessage = String.format(
                    "Node '%s' (type: %s) is suspended. Waiting for input parameters: %s.",
                    StringUtil.getFirstWithText(node.getName(), node.getId()),
                    node.getClass().getSimpleName(),
                    missingParams
            );

            throw new ChainSuspendException(errorMessage, suspendParameters);
        }

        return variables;
    }

    /**
     * 按实际访问惰性还原顶级 memory 值的只读映射。
     */
    private static final class LazyReferenceMap
            extends AbstractMap<String, Object> {

        private final Map<String, Object> delegate;
        private final Chain chain;
        /**
         * 同一模板渲染内已经还原的顶级值，避免重复引用触发重复分块读取。
         */
        private final Map<Object, Object> resolvedValues =
                new HashMap<>();

        /**
         * 创建惰性引用映射。
         *
         * @param delegate 原始运行时 memory
         * @param chain 当前工作流链路
         */
        private LazyReferenceMap(
                Map<String, Object> delegate,
                Chain chain) {
            this.delegate = delegate;
            this.chain = chain;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public synchronized Object get(Object key) {
            if (!delegate.containsKey(key)) {
                return null;
            }
            if (resolvedValues.containsKey(key)) {
                return resolvedValues.get(key);
            }
            Object resolved =
                    chain.resolveResultReferences(
                            delegate.get(key));
            resolvedValues.put(key, resolved);
            return resolved;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean containsKey(Object key) {
            return delegate.containsKey(key);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean isEmpty() {
            return delegate.isEmpty();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int size() {
            return delegate.size();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Set<String> keySet() {
            return Collections.unmodifiableSet(
                    delegate.keySet());
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Set<Entry<String, Object>> entrySet() {
            Set<String> keys = keySet();
            return new AbstractSet<>() {
                @Override
                public Iterator<Entry<String, Object>>
                iterator() {
                    Iterator<String> iterator =
                            keys.iterator();
                    return new Iterator<>() {
                        @Override
                        public boolean hasNext() {
                            return iterator
                                    .hasNext();
                        }

                        @Override
                        public Entry<String, Object>
                        next() {
                            String key =
                                    iterator.next();
                            return lazyEntry(key);
                        }
                    };
                }

                @Override
                public int size() {
                    return keys.size();
                }
            };
        }

        /**
         * 创建仅在读取值时还原引用的不可变条目。
         *
         * @param key memory 键
         * @return 惰性条目
         */
        private Entry<String, Object> lazyEntry(
                String key) {
            return new Entry<>() {
                @Override
                public String getKey() {
                    return key;
                }

                @Override
                public Object getValue() {
                    return LazyReferenceMap.this
                            .get(key);
                }

                @Override
                public Object setValue(Object value) {
                    throw new UnsupportedOperationException(
                            "read-only runtime memory");
                }

                @Override
                public boolean equals(Object value) {
                    return value instanceof Entry<?, ?> entry
                            && Objects.equals(
                            key, entry.getKey())
                            && Objects.equals(
                            getValue(),
                            entry.getValue());
                }

                @Override
                public int hashCode() {
                    return Objects.hashCode(key)
                            ^ Objects.hashCode(
                            getValue());
                }
            };
        }
    }


    public static class ChainSerializer implements ObjectSerializer {
        @Override
        public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
            if (object == null) {
                serializer.writeNull();
                return;
            }
            ChainState chain = (ChainState) object;
            serializer.write(chain.toJSON());
        }
    }

    public static class ChainDeserializer implements ObjectDeserializer {
        @Override
        public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
            String value = parser.parseObject(String.class);
            //noinspection unchecked
            return (T) ChainState.fromJSON(value);
        }
    }


    @Override
    public String toString() {
        return "ChainState{" +
                "instanceId='" + instanceId + '\'' +
                ", chainDefinitionId='" + chainDefinitionId + '\'' +
                ", memory=" + memory +
                ", executeResult=" + executeResult +
                ", environment=" + environment +
                ", computeCost=" + computeCost +
                ", suspendNodeIds=" + suspendNodeIds +
                ", suspendForParameters=" + suspendForParameters +
                ", status=" + status +
                ", message='" + message + '\'' +
                ", error=" + error +
                ", version=" + version +
                ", startedAt=" + startedAt +
                ", childExecutionCount=" + childExecutionCount +
                '}';
    }
}
