package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.exporter.JsonExporter;
import click.kamil.springstatemachineexporter.service.ExportService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.StreamSupport;

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

/**
 * Audit dedicated and expanded REST endpoints across dispatcher samples: none of the
 * known concrete endpoints should remain {@code AMBIGUOUS_WIDEN}.
 */
class ExportLinkResolutionAuditTest {

    @Test
    void enterpriseOrderMachineKnownEndpointsShouldNotBeAmbiguousWiden(@TempDir Path tempDir) throws Exception {
        JsonNode orderMachine = exportMachine(
                tempDir, "state_machines/state_machine_enterprise", "OrderStateMachineConfiguration");

        List<String> mustResolve = List.of(
                "POST /api/machine/order/pay",
                "POST /api/machine/order/ship",
                "POST /api/machine/ORDER/transition/PAY",
                "POST /api/machine/ORDER/transition/SHIP");

        for (String endpoint : mustResolve) {
            JsonNode chain = findChainByEndpoint(orderMachine, endpoint);
            assertThat(chain).as("chain for %s", endpoint).isNotNull();
            assertThat(chain.path("linkResolution").asText())
                    .as("linkResolution for %s", endpoint)
                    .isEqualTo("RESOLVED");
            assertThat(chain.path("matchedTransitions").isArray()).isTrue();
            assertThat(chain.path("matchedTransitions")).isNotEmpty();
        }

        StreamSupport.stream(orderMachine.path("metadata").path("callChains").spliterator(), false)
                .filter(chain -> chain.path("entryPoint").path("name").asText().contains("/transition/{event}"))
                .forEach(chain -> assertThat(chain.path("linkResolution").asText())
                        .isIn("UNRESOLVED_EXTERNAL", "AMBIGUOUS_WIDEN", "NO_MATCH"));
    }

    @Test
    void layeredDispatcherKnownEndpointsShouldNotBeAmbiguousWiden(@TempDir Path tempDir) throws Exception {
        JsonNode orderMachine = exportMachine(
                tempDir, "state_machines/layered_dispatcher_sample", "StandardOrderStateMachineConfiguration");

        List<String> mustResolve = List.of(
                "POST /api/orders/pay",
                "POST /api/orders/ship",
                "POST /api/orders/cancel",
                "POST /api/orders/rich/pay",
                "POST /api/orders/rich/ship",
                "POST /api/string-dispatch/orders/pay",
                "POST /api/string-dispatch/orders/ship",
                "POST /api/commands/order.pay",
                "POST /api/commands/order.ship");

        Map<String, String> failures = new LinkedHashMap<>();
        for (String endpoint : mustResolve) {
            JsonNode chain = findChainByEndpoint(orderMachine, endpoint);
            if (chain == null) {
                failures.put(endpoint, "missing chain");
                continue;
            }
            String resolution = chain.path("linkResolution").asText();
            if (!"RESOLVED".equals(resolution)) {
                failures.put(endpoint, resolution + " poly="
                        + chain.path("triggerPoint").path("polymorphicEvents"));
            }
        }
        assertThat(failures).isEmpty();
    }

    private static JsonNode exportMachine(Path tempDir, String sampleRelativePath, String configBaseName)
            throws Exception {
        Path sampleRoot = findProjectRoot().resolve(sampleRelativePath);
        ExportService exportService = new ExportService(List.of(new JsonExporter()));
        exportService.runExporter(sampleRoot, tempDir, List.of("json"), true, List.of(), null, null,
                click.kamil.springstatemachineexporter.exporter.EnumFormat.fn,
                click.kamil.springstatemachineexporter.exporter.EnumFormat.fn);
        return readMachineJson(tempDir, configBaseName, new ObjectMapper());
    }

    private static JsonNode findChainByEndpoint(JsonNode machineJson, String endpointName) {
        return StreamSupport.stream(machineJson.path("metadata").path("callChains").spliterator(), false)
                .filter(chain -> endpointName.equals(chain.path("entryPoint").path("name").asText()))
                .findFirst()
                .orElse(null);
    }

    private static JsonNode readMachineJson(Path outputDir, String configBaseName, ObjectMapper mapper)
            throws Exception {
        Path machineDir;
        try (var stream = Files.list(outputDir)) {
            machineDir = stream
                    .filter(Files::isDirectory)
                    .filter(path -> path.getFileName().toString().endsWith(configBaseName))
                    .findFirst()
                    .orElseThrow(() -> new IllegalStateException("No output for " + configBaseName));
        }
        return mapper.readTree(machineDir.resolve(machineDir.getFileName() + ".json").toFile());
    }

    private static Path findProjectRoot() {
        Path current = Path.of(".").toAbsolutePath();
        while (current != null && !Files.exists(current.resolve("settings.gradle"))) {
            current = current.getParent();
        }
        return current;
    }
}
