/**
 * 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.test;

import com.easyagents.flow.core.chain.Chain;
import com.easyagents.flow.core.chain.ChainDefinition;
import com.easyagents.flow.core.chain.ChainStatus;
import com.easyagents.flow.core.chain.Edge;
import com.easyagents.flow.core.chain.ChainState;
import com.easyagents.flow.core.chain.event.ChainEndEvent;
import com.easyagents.flow.core.chain.repository.ChainDefinitionSnapshotRepository;
import com.easyagents.flow.core.chain.repository.ChainStateField;
import com.easyagents.flow.core.chain.repository.ChainStateRepository;
import com.easyagents.flow.core.chain.repository.InMemoryChainDefinitionSnapshotRepository;
import com.easyagents.flow.core.chain.repository.InMemoryChainStateRepository;
import com.easyagents.flow.core.chain.repository.InMemoryNodeStateRepository;
import com.easyagents.flow.core.chain.runtime.ChainExecutor;
import com.easyagents.flow.core.chain.runtime.InMemoryTriggerStore;
import com.easyagents.flow.core.chain.runtime.Trigger;
import com.easyagents.flow.core.chain.runtime.TriggerScheduler;
import com.easyagents.flow.core.node.EndNode;
import com.easyagents.flow.core.node.BaseNode;
import com.easyagents.flow.core.node.ConfirmNode;
import com.easyagents.flow.core.node.StartNode;
import org.junit.Assert;
import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * {@link ChainExecutor} 并发同步执行测试。
 */
public class ChainExecutorConcurrencyTest {

    /**
     * 验证同步 Tool 入口遇到人工挂起会快速失败，不会无限占用调用线程。
     *
     * @throws Exception 异步测试执行失败时抛出
     */
    @Test
    public void shouldFailFastWhenNonSuspendingExecutionIsSuspended()
            throws Exception {
        ScheduledExecutorService schedulerPool = Executors.newSingleThreadScheduledExecutor();
        ExecutorService workerPool = Executors.newFixedThreadPool(2);
        TriggerScheduler triggerScheduler = new TriggerScheduler(
                new InMemoryTriggerStore(), schedulerPool, workerPool, 10L);
        ChainDefinition definition = createConfirmDefinition();
        ChainExecutor executor = new ChainExecutor(
                ignored -> definition,
                new InMemoryChainStateRepository(),
                new InMemoryNodeStateRepository(),
                triggerScheduler);
        ExecutorService caller = Executors.newSingleThreadExecutor();
        try {
            Future<Map<String, Object>> result = caller.submit(
                    () -> executor.executeWithoutSuspension(
                            definition.getId(), Collections.emptyMap()));
            try {
                result.get(3, TimeUnit.SECONDS);
                Assert.fail("suspended workflow must fail");
            } catch (ExecutionException exception) {
                Assert.assertTrue(
                        String.valueOf(exception.getCause().getMessage())
                                .contains("Execution failed"));
            }
        } finally {
            caller.shutdownNow();
            triggerScheduler.shutdown();
        }
    }

    /**
     * 验证实例初始化会在入口触发器创建前持久化工作流定义 ID。
     *
     * @throws Exception 执行器初始化或异步启动失败时抛出
     */
    @Test
    public void shouldPersistDefinitionIdBeforeStartingWorkflow() throws Exception {
        ScheduledExecutorService schedulerPool = Executors.newSingleThreadScheduledExecutor();
        ExecutorService workerPool = Executors.newFixedThreadPool(2);
        TriggerScheduler triggerScheduler = new TriggerScheduler(
                new InMemoryTriggerStore(), schedulerPool, workerPool, 1000L);
        ChainDefinition definition = createDefinition();
        InMemoryChainStateRepository stateRepository =
                new InMemoryChainStateRepository();
        ChainExecutor chainExecutor = new ChainExecutor(
                id -> definition,
                stateRepository,
                new InMemoryNodeStateRepository(),
                triggerScheduler);
        AtomicReference<String> persistedDefinitionId = new AtomicReference<>();

        try {
            chainExecutor.executeAsync(
                    definition.getId(),
                    Collections.emptyMap(),
                    executeId -> persistedDefinitionId.set(
                            stateRepository.load(executeId).getChainDefinitionId())
            );

            Assert.assertEquals(
                    definition.getId(),
                    persistedDefinitionId.get());
        } finally {
            triggerScheduler.shutdown();
        }
    }

    /**
     * 验证历史异常实例缺少定义 ID 时，死信触发器仍能收敛为失败终态。
     *
     * @throws Exception 反射调用死信收口逻辑失败时抛出
     */
    @Test
    public void shouldFailDeadLetteredWorkflowWithoutDefinitionId()
            throws Exception {
        ScheduledExecutorService schedulerPool = Executors.newSingleThreadScheduledExecutor();
        ExecutorService workerPool = Executors.newFixedThreadPool(2);
        TriggerScheduler triggerScheduler = new TriggerScheduler(
                new InMemoryTriggerStore(), schedulerPool, workerPool, 1000L);
        InMemoryChainStateRepository stateRepository =
                new InMemoryChainStateRepository();
        AtomicInteger definitionLoadCount = new AtomicInteger();
        ChainExecutor chainExecutor = new ChainExecutor(
                id -> {
                    definitionLoadCount.incrementAndGet();
                    return null;
                },
                stateRepository,
                new InMemoryNodeStateRepository(),
                triggerScheduler);
        String instanceId = "dead-letter-missing-definition";
        stateRepository.create(instanceId);
        Trigger trigger = new Trigger();
        trigger.setId("dead-letter-trigger");
        trigger.setStateInstanceId(instanceId);
        Method failDeadLetteredTrigger = ChainExecutor.class.getDeclaredMethod(
                "failDeadLetteredTrigger",
                Trigger.class,
                Throwable.class);
        failDeadLetteredTrigger.setAccessible(true);

        try {
            boolean finalized = (boolean) failDeadLetteredTrigger.invoke(
                    chainExecutor,
                    trigger,
                    new IllegalStateException("delivery attempts exhausted"));

            Assert.assertTrue(finalized);
            Assert.assertEquals(
                    ChainStatus.FAILED,
                    stateRepository.load(instanceId).getStatus());
            Assert.assertEquals(0, definitionLoadCount.get());
        } finally {
            triggerScheduler.shutdown();
        }
    }

    /**
     * 验证多个同步调用可以通过实例 ID 独立接收执行结果。
     *
     * @throws Exception 并发任务执行失败、超时或中断时抛出
     */
    @Test
    public void shouldRouteConcurrentSynchronousExecutionResults() throws Exception {
        ScheduledExecutorService schedulerPool = Executors.newScheduledThreadPool(2);
        ExecutorService workerPool = Executors.newFixedThreadPool(8);
        ExecutorService callerPool = Executors.newFixedThreadPool(8);
        TriggerScheduler triggerScheduler = new TriggerScheduler(
                new InMemoryTriggerStore(), schedulerPool, workerPool, 1000L);
        ChainDefinition definition = createDefinition();
        ChainExecutor chainExecutor = new ChainExecutor(
                id -> definition,
                new InMemoryChainStateRepository(),
                new InMemoryNodeStateRepository(),
                triggerScheduler);

        int executionCount = 16;
        CountDownLatch startGate = new CountDownLatch(1);
        List<Future<Map<String, Object>>> executions = new ArrayList<>(executionCount);

        try {
            for (int index = 0; index < executionCount; index++) {
                executions.add(callerPool.submit(() -> {
                    startGate.await();
                    return chainExecutor.execute(definition.getId(), Collections.emptyMap());
                }));
            }
            startGate.countDown();

            for (Future<Map<String, Object>> execution : executions) {
                Assert.assertNotNull(execution.get(10, TimeUnit.SECONDS));
            }
        } finally {
            startGate.countDown();
            callerPool.shutdownNow();
            triggerScheduler.shutdown();
        }
    }

    /**
     * 验证一个工作流实例的多个节点触发会复用启动时的定义快照。
     */
    @Test
    public void shouldReuseDefinitionSnapshotAcrossNodeTriggers() {
        ScheduledExecutorService schedulerPool = Executors.newSingleThreadScheduledExecutor();
        ExecutorService workerPool = Executors.newFixedThreadPool(2);
        TriggerScheduler triggerScheduler = new TriggerScheduler(
                new InMemoryTriggerStore(), schedulerPool, workerPool, 1000L);
        ChainDefinition definition = createDefinition();
        AtomicInteger definitionLoadCount = new AtomicInteger();
        ChainExecutor chainExecutor = new ChainExecutor(
                id -> {
                    definitionLoadCount.incrementAndGet();
                    return definition;
                },
                new InMemoryChainStateRepository(),
                new InMemoryNodeStateRepository(),
                triggerScheduler);

        try {
            Map<String, Object> result = chainExecutor.execute(
                    definition.getId(), Collections.emptyMap(), 10, TimeUnit.SECONDS);

            Assert.assertNotNull(result);
            Assert.assertEquals(1, definitionLoadCount.get());
        } finally {
            triggerScheduler.shutdown();
        }
    }

    /**
     * 验证取消期间已经完成的节点 I/O 不会继续推进下游节点。
     *
     * @throws Exception 等待异步节点进入或退出失败时抛出
     */
    @Test
    public void shouldNotAdvanceAfterCancellationDuringNodeIo() throws Exception {
        ScheduledExecutorService schedulerPool = Executors.newScheduledThreadPool(2);
        ExecutorService workerPool = Executors.newFixedThreadPool(2);
        TriggerScheduler triggerScheduler = new TriggerScheduler(
                new InMemoryTriggerStore(), schedulerPool, workerPool, 1000L);
        InMemoryChainStateRepository chainStateRepository = new InMemoryChainStateRepository();
        CountDownLatch ioStarted = new CountDownLatch(1);
        CountDownLatch allowIoCompletion = new CountDownLatch(1);
        AtomicInteger downstreamExecutions = new AtomicInteger();
        AtomicInteger chainEndEvents = new AtomicInteger();
        ChainDefinition definition = createCancellationDefinition(
                ioStarted, allowIoCompletion, downstreamExecutions);
        ChainExecutor chainExecutor = new ChainExecutor(
                id -> definition,
                chainStateRepository,
                new InMemoryNodeStateRepository(),
                triggerScheduler);
        chainExecutor.addEventListener((event, chain) -> {
            if (event instanceof ChainEndEvent) {
                chainEndEvents.incrementAndGet();
            }
        });

        try {
            String instanceId = chainExecutor.executeAsync(
                    definition.getId(), Collections.emptyMap());
            Assert.assertTrue(ioStarted.await(2, TimeUnit.SECONDS));
            Assert.assertTrue(chainExecutor.cancel(instanceId, "test cancellation"));
            allowIoCompletion.countDown();

            Thread.sleep(200L);
            Assert.assertEquals(0, downstreamExecutions.get());
            Assert.assertEquals(
                    ChainStatus.CANCELLED,
                    chainStateRepository.load(instanceId).getStatus());
            Assert.assertEquals(1, chainEndEvents.get());
        } finally {
            allowIoCompletion.countDown();
            triggerScheduler.shutdown();
        }
    }

    /**
     * 验证同一节点执行期间重复读取状态会复用执行快照。
     */
    @Test
    public void shouldKeepPublicStateReadsFreshDuringNodeExecution() {
        ScheduledExecutorService schedulerPool = Executors.newSingleThreadScheduledExecutor();
        ExecutorService workerPool = Executors.newSingleThreadExecutor();
        TriggerScheduler triggerScheduler = new TriggerScheduler(
                new InMemoryTriggerStore(), schedulerPool, workerPool, 1000L);
        CountingChainStateRepository stateRepository = new CountingChainStateRepository();
        AtomicInteger nodeStateLoads = new AtomicInteger(-1);
        ChainDefinition definition = createStateReadDefinition(
                stateRepository.loadCount,
                nodeStateLoads,
                false);
        ChainExecutor chainExecutor = new ChainExecutor(
                id -> definition,
                stateRepository,
                new InMemoryNodeStateRepository(),
                triggerScheduler);

        try {
            Map<String, Object> result = chainExecutor.execute(
                    definition.getId(), Collections.emptyMap(), 10, TimeUnit.SECONDS);

            Assert.assertNotNull(result);
            Assert.assertEquals(10, nodeStateLoads.get());
        } finally {
            triggerScheduler.shutdown();
        }
    }

    /**
     * 验证节点内部执行视图重复读取不会访问状态仓储。
     */
    @Test
    public void shouldReusePointInTimeStateDuringNodeExecution() {
        ScheduledExecutorService schedulerPool =
                Executors.newSingleThreadScheduledExecutor();
        ExecutorService workerPool =
                Executors.newSingleThreadExecutor();
        TriggerScheduler triggerScheduler =
                new TriggerScheduler(
                        new InMemoryTriggerStore(),
                        schedulerPool,
                        workerPool,
                        1000L);
        CountingChainStateRepository stateRepository =
                new CountingChainStateRepository();
        AtomicInteger nodeStateLoads =
                new AtomicInteger(-1);
        ChainDefinition definition =
                createStateReadDefinition(
                        stateRepository.loadCount,
                        nodeStateLoads,
                        true);
        ChainExecutor chainExecutor =
                new ChainExecutor(
                        id -> definition,
                        stateRepository,
                        new InMemoryNodeStateRepository(),
                        triggerScheduler);

        try {
            Map<String, Object> result =
                    chainExecutor.execute(
                            definition.getId(),
                            Collections.emptyMap(),
                            10,
                            TimeUnit.SECONDS);

            Assert.assertNotNull(result);
            Assert.assertEquals(
                    0, nodeStateLoads.get());
        } finally {
            triggerScheduler.shutdown();
        }
    }

    /**
     * 验证升级前 parent-linked 状态首次解析顶级审计 ID 后会持久复用。
     */
    @Test
    public void shouldBackfillLegacyAuditInstanceIdOnce() {
        CountingChainStateRepository repository =
                new CountingChainStateRepository();
        ChainState root =
                repository.create("audit-root");
        ChainState child =
                repository.create("audit-child");
        child.setParentInstanceId(
                root.getInstanceId());
        child.setAuditInstanceId(null);
        ChainDefinition definition =
                new ChainDefinition();
        definition.setId("audit-definition");
        Chain chain =
                new Chain(
                        definition,
                        child.getInstanceId());
        chain.setChainStateRepository(
                repository);

        Assert.assertEquals(
                root.getInstanceId(),
                chain.getAuditInstanceId());
        int loadsAfterBackfill =
                repository.loadCount.get();
        Assert.assertEquals(
                root.getInstanceId(),
                chain.getAuditInstanceId());

        Assert.assertEquals(
                loadsAfterBackfill + 1,
                repository.loadCount.get());
        Assert.assertEquals(
                root.getInstanceId(),
                repository.load(
                        child.getInstanceId())
                        .getAuditInstanceId());
    }

    /**
     * 验证实例状态初始化失败时会清理进程缓存和持久定义快照。
     */
    @Test
    public void shouldCleanupDefinitionSnapshotWhenInitializationFails() throws Exception {
        ScheduledExecutorService schedulerPool = Executors.newSingleThreadScheduledExecutor();
        ExecutorService workerPool = Executors.newSingleThreadExecutor();
        TriggerScheduler triggerScheduler = new TriggerScheduler(
                new InMemoryTriggerStore(), schedulerPool, workerPool, 1000L);
        TrackingSnapshotRepository snapshotRepository = new TrackingSnapshotRepository();
        ChainStateRepository failingStateRepository = new ChainStateRepository() {
            @Override
            public ChainState load(String instanceId) {
                return null;
            }

            @Override
            public ChainState create(String instanceId) {
                throw new IllegalStateException("initialization failed");
            }

            @Override
            public boolean tryUpdate(
                    ChainState newState, EnumSet<ChainStateField> fields) {
                return false;
            }
        };
        ChainExecutor executor = new ChainExecutor(
                id -> createDefinition(),
                failingStateRepository,
                new InMemoryNodeStateRepository(),
                null,
                snapshotRepository,
                triggerScheduler,
                null);

        try {
            executor.executeAsync("concurrent-sync-test", Collections.emptyMap());
            Assert.fail("initialization failure must be propagated");
        } catch (IllegalStateException expected) {
            Assert.assertEquals("initialization failed", expected.getMessage());
        } finally {
            triggerScheduler.shutdown();
        }

        Assert.assertTrue(snapshotRepository.snapshots.isEmpty());
        Assert.assertTrue(activeDefinitions(executor).isEmpty());
    }

    /**
     * 验证进程内定义热点缓存有固定上限，持久快照仍保留恢复能力。
     */
    @Test
    public void shouldBoundActiveDefinitionCache() throws Exception {
        ScheduledExecutorService schedulerPool = Executors.newSingleThreadScheduledExecutor();
        ExecutorService workerPool = Executors.newSingleThreadExecutor();
        TriggerScheduler triggerScheduler = new TriggerScheduler(
                new InMemoryTriggerStore(), schedulerPool, workerPool, 1000L);
        InMemoryChainDefinitionSnapshotRepository snapshotRepository =
                new InMemoryChainDefinitionSnapshotRepository();
        ChainExecutor executor = new ChainExecutor(
                id -> createDefinition(),
                new InMemoryChainStateRepository(),
                new InMemoryNodeStateRepository(),
                null,
                snapshotRepository,
                triggerScheduler,
                null);
        java.lang.reflect.Method createChain = ChainExecutor.class.getDeclaredMethod(
                "createChain", ChainDefinition.class);
        createChain.setAccessible(true);

        try {
            for (int index = 0; index < 1100; index++) {
                createChain.invoke(executor, createDefinition());
            }
        } finally {
            triggerScheduler.shutdown();
        }

        Assert.assertEquals(1024, activeDefinitions(executor).size());
    }

    /**
     * 读取执行器内部定义缓存，供资源上限回归验证。
     *
     * @param executor 工作流执行器
     * @return 当前定义缓存
     * @throws Exception 反射访问失败时抛出
     */
    @SuppressWarnings("unchecked")
    private Map<String, ChainDefinition> activeDefinitions(
            ChainExecutor executor) throws Exception {
        Field field = ChainExecutor.class.getDeclaredField("activeDefinitions");
        field.setAccessible(true);
        return (Map<String, ChainDefinition>) field.get(executor);
    }

    /**
     * 创建仅包含开始和结束节点的测试工作流。
     *
     * @return 测试工作流定义
     */
    private ChainDefinition createDefinition() {
        ChainDefinition definition = new ChainDefinition();
        definition.setId("concurrent-sync-test");

        StartNode startNode = new StartNode();
        startNode.setId("start");
        EndNode endNode = new EndNode();
        endNode.setId("end");

        Edge edge = new Edge();
        edge.setId("start-to-end");
        edge.setSource(startNode.getId());
        edge.setTarget(endNode.getId());

        definition.addNode(startNode);
        definition.addNode(endNode);
        definition.addEdge(edge);
        return definition;
    }

    /**
     * 创建包含内部确认节点的测试 Workflow。
     *
     * @return 会进入挂起状态的 Workflow 定义
     */
    private ChainDefinition createConfirmDefinition() {
        ChainDefinition definition = new ChainDefinition();
        definition.setId("non-suspending-confirm-test");
        StartNode start = new StartNode();
        start.setId("start");
        ConfirmNode confirm = new ConfirmNode();
        confirm.setId("confirm");
        EndNode end = new EndNode();
        end.setId("end");
        Edge first = new Edge();
        first.setId("start-to-confirm");
        first.setSource("start");
        first.setTarget("confirm");
        Edge second = new Edge();
        second.setId("confirm-to-end");
        second.setSource("confirm");
        second.setTarget("end");
        definition.addNode(start);
        definition.addNode(confirm);
        definition.addNode(end);
        definition.addEdge(first);
        definition.addEdge(second);
        return definition;
    }

    /**
     * 创建用于取消传播验证的工作流。
     *
     * @param ioStarted I/O 节点进入信号
     * @param allowIoCompletion I/O 节点退出信号
     * @param downstreamExecutions 下游执行计数
     * @return 测试工作流定义
     */
    private ChainDefinition createCancellationDefinition(CountDownLatch ioStarted,
                                                         CountDownLatch allowIoCompletion,
                                                         AtomicInteger downstreamExecutions) {
        ChainDefinition definition = new ChainDefinition();
        definition.setId("cancellation-test");

        StartNode start = new StartNode();
        start.setId("start");
        BaseNode blocking = new BaseNode() {
            @Override
            public Map<String, Object> execute(Chain chain) {
                ioStarted.countDown();
                try {
                    if (!allowIoCompletion.await(2, TimeUnit.SECONDS)) {
                        throw new IllegalStateException("test I/O release timed out");
                    }
                } catch (InterruptedException error) {
                    Thread.currentThread().interrupt();
                    throw new IllegalStateException("test I/O interrupted", error);
                }
                return Collections.singletonMap("value", "completed");
            }
        };
        blocking.setId("blocking");
        BaseNode downstream = new BaseNode() {
            @Override
            public Map<String, Object> execute(Chain chain) {
                downstreamExecutions.incrementAndGet();
                return Collections.emptyMap();
            }
        };
        downstream.setId("downstream");
        EndNode end = new EndNode();
        end.setId("end");

        definition.addNode(start);
        definition.addNode(blocking);
        definition.addNode(downstream);
        definition.addNode(end);
        definition.addEdge(edge("start-blocking", "start", "blocking"));
        definition.addEdge(edge("blocking-downstream", "blocking", "downstream"));
        definition.addEdge(edge("downstream-end", "downstream", "end"));
        return definition;
    }

    /**
     * 创建包含重复状态读取节点的测试工作流。
     *
     * @param repositoryLoadCount 仓储读取计数
     * @param nodeStateLoads 节点执行期间新增的读取次数
     * @param useExecutionView 是否读取节点 point-in-time 执行视图
     * @return 测试工作流定义
     */
    private ChainDefinition createStateReadDefinition(AtomicInteger repositoryLoadCount,
                                                      AtomicInteger nodeStateLoads,
                                                      boolean useExecutionView) {
        ChainDefinition definition = new ChainDefinition();
        definition.setId("state-snapshot-test");

        StartNode start = new StartNode();
        start.setId("start");
        BaseNode repeatedReader = new BaseNode() {
            @Override
            public Map<String, Object> execute(Chain chain) {
                int before = repositoryLoadCount.get();
                for (int index = 0; index < 10; index++) {
                    Assert.assertNotNull(
                            useExecutionView
                                    ? chain.getExecutionState()
                                    : chain.getState());
                }
                nodeStateLoads.set(repositoryLoadCount.get() - before);
                return Collections.emptyMap();
            }
        };
        repeatedReader.setId("reader");
        EndNode end = new EndNode();
        end.setId("end");

        definition.addNode(start);
        definition.addNode(repeatedReader);
        definition.addNode(end);
        definition.addEdge(edge("start-reader", "start", "reader"));
        definition.addEdge(edge("reader-end", "reader", "end"));
        return definition;
    }

    /**
     * 创建测试边。
     *
     * @param id 边 ID
     * @param source 源节点 ID
     * @param target 目标节点 ID
     * @return 测试边
     */
    private Edge edge(String id, String source, String target) {
        Edge edge = new Edge();
        edge.setId(id);
        edge.setSource(source);
        edge.setTarget(target);
        return edge;
    }

    /**
     * 记录定义快照生命周期的测试仓储。
     */
    private static final class TrackingSnapshotRepository
            implements ChainDefinitionSnapshotRepository {

        private final Map<String, ChainDefinition> snapshots =
                new LinkedHashMap<>();

        @Override
        public void save(String instanceId, ChainDefinition definition) {
            snapshots.put(instanceId, definition);
        }

        @Override
        public ChainDefinition load(String instanceId) {
            return snapshots.get(instanceId);
        }

        @Override
        public void remove(String instanceId) {
            snapshots.remove(instanceId);
        }
    }

    /**
     * 记录状态读取次数的测试仓储。
     */
    private static final class CountingChainStateRepository
            extends InMemoryChainStateRepository {

        private final AtomicInteger loadCount = new AtomicInteger();

        @Override
        public ChainState load(String instanceId) {
            loadCount.incrementAndGet();
            return super.load(instanceId);
        }
    }
}
