package com.agent.management.engine;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

class NodeInputResolverTest {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    private NodeWorkspace workspaceWith(Map<String, Map<String, Object>> scoped) {
        NodeWorkspace ws = new NodeWorkspace();
        for (Map.Entry<String, Map<String, Object>> e : scoped.entrySet()) {
            ws.getScopedOutputs().put(e.getKey(), e.getValue());
            for (Map.Entry<String, Object> f : e.getValue().entrySet()) {
                ws.getVariables().put(e.getKey() + NodeWorkspaceBuilder.NODE_FIELD_SEPARATOR + f.getKey(), f.getValue());
            }
        }
        return ws;
    }

    @Test
    void explicitMappingResolvesScopedOutput() throws Exception {
        NodeWorkspace ws = workspaceWith(Map.of(
                "nodeA", Map.of("result", "fromA")
        ));
        JsonNode data = MAPPER.readTree("{\"inputs\":[{\"name\":\"input1\",\"type\":\"string\",\"required\":true,\"mapping\":{\"sourceNodeId\":\"nodeA\",\"sourceField\":\"result\"}}]}");

        NodeInputResolver.ResolvedInputs resolved = NodeInputResolver.resolveInputs(data, ws);
        assertThat(resolved.hasMissing()).isFalse();
        assertThat(resolved.getValues()).containsEntry("input1", "fromA");
    }

    @Test
    void explicitMappingWithSourcePathExtractsNestedValue() throws Exception {
        NodeWorkspace ws = workspaceWith(Map.of(
                "nodeA", Map.of("data", Map.of("total", 100))
        ));
        JsonNode data = MAPPER.readTree("{\"inputs\":[{\"name\":\"input1\",\"type\":\"number\",\"mapping\":{\"sourceNodeId\":\"nodeA\",\"sourceField\":\"data\",\"sourcePath\":\"total\"}}]}");

        NodeInputResolver.ResolvedInputs resolved = NodeInputResolver.resolveInputs(data, ws);
        assertThat(resolved.getValues()).containsEntry("input1", 100);
    }

    @Test
    void unreachableExplicitMappingFallsBackToImplicit() throws Exception {
        NodeWorkspace ws = workspaceWith(Map.of(
                "nodeA", Map.of("result", "fromA")
        ));
        JsonNode data = MAPPER.readTree("{\"inputs\":[{\"name\":\"result\",\"type\":\"string\",\"mapping\":{\"sourceNodeId\":\"nodeZ\",\"sourceField\":\"x\"}}]}");

        NodeInputResolver.ResolvedInputs resolved = NodeInputResolver.resolveInputs(data, ws);
        assertThat(resolved.getValues()).containsEntry("result", "fromA");
    }

    @Test
    void implicitExactMatch() throws Exception {
        NodeWorkspace ws = workspaceWith(Map.of(
                "nodeA", Map.of("result", "fromA")
        ));
        JsonNode data = MAPPER.readTree("{\"inputs\":[{\"name\":\"result\",\"type\":\"string\"}]}");

        NodeInputResolver.ResolvedInputs resolved = NodeInputResolver.resolveInputs(data, ws);
        assertThat(resolved.getValues()).containsEntry("result", "fromA");
    }

    @Test
    void implicitNormalizedMatchIgnoresUnderscoreAndCase() throws Exception {
        NodeWorkspace ws = workspaceWith(Map.of(
                "nodeA", Map.of("myResult", "fromA")
        ));
        JsonNode data = MAPPER.readTree("{\"inputs\":[{\"name\":\"my_result\",\"type\":\"string\"}]}");

        NodeInputResolver.ResolvedInputs resolved = NodeInputResolver.resolveInputs(data, ws);
        assertThat(resolved.getValues()).containsEntry("my_result", "fromA");
    }

    @Test
    void implicitJsonPathExactMatch() throws Exception {
        NodeWorkspace ws = workspaceWith(Map.of(
                "nodeA", Map.of("data", Map.of("nested", Map.of("value", 42)))
        ));
        JsonNode data = MAPPER.readTree("{\"inputs\":[{\"name\":\"data.nested.value\",\"type\":\"number\"}]}");

        NodeInputResolver.ResolvedInputs resolved = NodeInputResolver.resolveInputs(data, ws);
        assertThat(resolved.getValues()).containsEntry("data.nested.value", 42);
    }

    @Test
    void missingRequiredInputsAreReported() throws Exception {
        NodeWorkspace ws = workspaceWith(Map.of());
        JsonNode data = MAPPER.readTree("{\"inputs\":[{\"name\":\"requiredInput\",\"type\":\"string\",\"required\":true}]}");

        NodeInputResolver.ResolvedInputs resolved = NodeInputResolver.resolveInputs(data, ws);
        assertThat(resolved.hasMissing()).isTrue();
        assertThat(resolved.getMissing()).containsExactly("requiredInput");
    }

    @Test
    void directVariableFallback() throws Exception {
        NodeWorkspace ws = new NodeWorkspace();
        ws.getVariables().put("initial", "direct");
        JsonNode data = MAPPER.readTree("{\"inputs\":[{\"name\":\"initial\",\"type\":\"string\"}]}");

        NodeInputResolver.ResolvedInputs resolved = NodeInputResolver.resolveInputs(data, ws);
        assertThat(resolved.getValues()).containsEntry("initial", "direct");
    }
}
