package com.cameleer.server.core.detail;

import com.cameleer.server.core.storage.ExecutionStore;
import com.cameleer.server.core.storage.ExecutionStore.ProcessorRecord;
import org.junit.jupiter.api.Test;

import java.time.Instant;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

/**
 * Unit tests for {@link DetailService#buildTree} logic.
 * <p>
 * Verifies correct parent-child wiring from flat ProcessorRecord lists.
 */
class TreeReconstructionTest {

    private final DetailService detailService = new DetailService(mock(ExecutionStore.class));

    private static final Instant NOW = Instant.parse("2026-03-10T10:00:00Z");

    private ProcessorRecord proc(String id, String type, String status,
                                  int depth, String parentId) {
        return new ProcessorRecord(
                "exec-1", id, type,
                "default", "route1", depth, parentId,
                status, NOW, NOW, 10L,
                null, null, null, null, null, null, null, null, null,
                null, null, null, null, null,
                null, null, null, null, null, null, null, null,
                null, null, null, null, null, null
        );
    }

    @Test
    void linearChain_rootChildGrandchild() {
        List<ProcessorRecord> processors = List.of(
                proc("root", "log", "COMPLETED", 0, null),
                proc("child", "bean", "COMPLETED", 1, "root"),
                proc("grandchild", "to", "COMPLETED", 2, "child")
        );

        List<ProcessorNode> roots = detailService.buildTree(processors);

        assertThat(roots).hasSize(1);
        ProcessorNode root = roots.get(0);
        assertThat(root.getProcessorId()).isEqualTo("root");
        assertThat(root.getChildren()).hasSize(1);

        ProcessorNode child = root.getChildren().get(0);
        assertThat(child.getProcessorId()).isEqualTo("child");
        assertThat(child.getChildren()).hasSize(1);

        ProcessorNode grandchild = child.getChildren().get(0);
        assertThat(grandchild.getProcessorId()).isEqualTo("grandchild");
        assertThat(grandchild.getChildren()).isEmpty();
    }

    @Test
    void multipleRoots_noNesting() {
        List<ProcessorRecord> processors = List.of(
                proc("A", "log", "COMPLETED", 0, null),
                proc("B", "log", "COMPLETED", 0, null),
                proc("C", "log", "COMPLETED", 0, null)
        );

        List<ProcessorNode> roots = detailService.buildTree(processors);

        assertThat(roots).hasSize(3);
        assertThat(roots.get(0).getProcessorId()).isEqualTo("A");
        assertThat(roots.get(1).getProcessorId()).isEqualTo("B");
        assertThat(roots.get(2).getProcessorId()).isEqualTo("C");
        roots.forEach(r -> assertThat(r.getChildren()).isEmpty());
    }

    @Test
    void branchingTree_parentWithTwoChildren_secondChildHasGrandchild() {
        List<ProcessorRecord> processors = List.of(
                proc("parent", "split", "COMPLETED", 0, null),
                proc("child1", "log", "COMPLETED", 1, "parent"),
                proc("child2", "bean", "COMPLETED", 1, "parent"),
                proc("grandchild", "to", "COMPLETED", 2, "child2")
        );

        List<ProcessorNode> roots = detailService.buildTree(processors);

        assertThat(roots).hasSize(1);
        ProcessorNode parent = roots.get(0);
        assertThat(parent.getProcessorId()).isEqualTo("parent");
        assertThat(parent.getChildren()).hasSize(2);

        ProcessorNode child1 = parent.getChildren().get(0);
        assertThat(child1.getProcessorId()).isEqualTo("child1");
        assertThat(child1.getChildren()).isEmpty();

        ProcessorNode child2 = parent.getChildren().get(1);
        assertThat(child2.getProcessorId()).isEqualTo("child2");
        assertThat(child2.getChildren()).hasSize(1);

        ProcessorNode grandchild = child2.getChildren().get(0);
        assertThat(grandchild.getProcessorId()).isEqualTo("grandchild");
        assertThat(grandchild.getChildren()).isEmpty();
    }

    @Test
    void emptyList_producesEmptyRoots() {
        List<ProcessorNode> roots = detailService.buildTree(List.of());
        assertThat(roots).isEmpty();
    }

    // --- seq-based model tests (ClickHouse) ---

    private ProcessorRecord procWithSeq(String id, String type, String status,
                                         int seq, Integer parentSeq,
                                         Integer iteration, Integer iterationSize) {
        return new ProcessorRecord(
                "exec-1", id, type, "app", "route1",
                0, null, status, NOW, NOW, 10L,
                null, null, null, null, null, null, null, null, null,
                null, null, null, null, null,
                null, null, null, null, null, null, null, null,
                seq, parentSeq, iteration, iterationSize, null, null
        );
    }

    @Test
    void buildTree_seqBasedModel_linearChain() {
        List<ProcessorRecord> processors = List.of(
                procWithSeq("from", "from", "COMPLETED", 1, null, null, null),
                procWithSeq("log1", "log", "COMPLETED", 2, 1, null, null),
                procWithSeq("to1", "to", "COMPLETED", 3, 2, null, null)
        );

        List<ProcessorNode> roots = detailService.buildTree(processors);

        assertThat(roots).hasSize(1);
        ProcessorNode root = roots.get(0);
        assertThat(root.getProcessorId()).isEqualTo("from");
        assertThat(root.getChildren()).hasSize(1);

        ProcessorNode child = root.getChildren().get(0);
        assertThat(child.getProcessorId()).isEqualTo("log1");
        assertThat(child.getChildren()).hasSize(1);

        ProcessorNode grandchild = child.getChildren().get(0);
        assertThat(grandchild.getProcessorId()).isEqualTo("to1");
        assertThat(grandchild.getChildren()).isEmpty();
    }

    @Test
    void buildTree_seqBasedModel_sameProcessorIdMultipleIterations() {
        // A split processor (seq 1) with 3 child processors all having the SAME
        // processorId but different seq values — this is the key scenario that
        // breaks the old processorId-based approach.
        List<ProcessorRecord> processors = List.of(
                procWithSeq("split1", "split", "COMPLETED", 1, null, null, null),
                procWithSeq("log-inside", "log", "COMPLETED", 2, 1, 0, 3),
                procWithSeq("log-inside", "log", "COMPLETED", 3, 1, 1, 3),
                procWithSeq("log-inside", "log", "COMPLETED", 4, 1, 2, 3)
        );

        List<ProcessorNode> roots = detailService.buildTree(processors);

        assertThat(roots).hasSize(1);
        ProcessorNode split = roots.get(0);
        assertThat(split.getProcessorId()).isEqualTo("split1");
        assertThat(split.getChildren()).hasSize(3);

        // All three children should have the same processorId
        for (ProcessorNode child : split.getChildren()) {
            assertThat(child.getProcessorId()).isEqualTo("log-inside");
        }
    }

    @Test
    void buildTree_seqBasedModel_orphanSafety() {
        // A processor whose parentSeq points to a non-existent seq
        List<ProcessorRecord> processors = List.of(
                procWithSeq("root", "from", "COMPLETED", 1, null, null, null),
                procWithSeq("orphan", "log", "COMPLETED", 2, 999, null, null)
        );

        List<ProcessorNode> roots = detailService.buildTree(processors);

        // Both should be roots — the orphan falls through to root list
        assertThat(roots).hasSize(2);
        assertThat(roots.get(0).getProcessorId()).isEqualTo("root");
        assertThat(roots.get(1).getProcessorId()).isEqualTo("orphan");
    }

    @Test
    void buildTree_seqBasedModel_iterationFields() {
        // Verify iteration/iterationSize are populated on the correct nodes
        List<ProcessorRecord> processors = List.of(
                procWithSeq("split1", "split", "COMPLETED", 1, null, null, 3),
                procWithSeq("body", "log", "COMPLETED", 2, 1, 0, null),
                procWithSeq("body", "log", "COMPLETED", 3, 1, 1, null),
                procWithSeq("body", "log", "COMPLETED", 4, 1, 2, null)
        );

        List<ProcessorNode> roots = detailService.buildTree(processors);

        assertThat(roots).hasSize(1);
        ProcessorNode container = roots.get(0);
        assertThat(container.getIterationSize()).isEqualTo(3);
        assertThat(container.getIteration()).isNull();
        assertThat(container.getChildren()).hasSize(3);
        assertThat(container.getChildren().get(0).getIteration()).isEqualTo(0);
        assertThat(container.getChildren().get(1).getIteration()).isEqualTo(1);
        assertThat(container.getChildren().get(2).getIteration()).isEqualTo(2);
    }
}
