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.List;
import java.util.stream.StreamSupport;

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

/**
 * Regression for REST dispatcher chains: expanded concrete endpoints must not re-infer all
 * transition events; unresolved {@code {event}} templates stay external.
 */
class EnterpriseDispatcherMatchedTransitionsRegressionTest {

    @Test
    void astExportExpandedTransitionEndpointsShouldNotOverLink(@TempDir Path tempDir) throws Exception {
        Path enterprise = findProjectRoot().resolve("state_machines/state_machine_enterprise");
        ExportService exportService = new ExportService(List.of(new JsonExporter()));
        exportService.runExporter(enterprise, tempDir, List.of("json"), true, List.of(), null, null,
                click.kamil.springstatemachineexporter.exporter.EnumFormat.fn,
                click.kamil.springstatemachineexporter.exporter.EnumFormat.fn);

        JsonNode json = readMachineJson(tempDir, "OrderStateMachineConfiguration", new ObjectMapper());
        int transitionCount = json.path("transitions").size();

        StreamSupport.stream(json.path("metadata").path("callChains").spliterator(), false)
                .filter(chain -> chain.path("entryPoint").path("name").asText().contains("/transition/{event}"))
                .forEach(chain -> {
                    JsonNode matched = chain.path("matchedTransitions");
                    assertThat(matched.isNull() || (matched.isArray() && matched.isEmpty())).isTrue();
                    assertThat(chain.path("linkResolution").asText()).isEqualTo("UNRESOLVED_EXTERNAL");
                });

        JsonNode orderPay = findChainByEndpoint(json, "POST /api/machine/ORDER/transition/PAY");
        assertThat(orderPay.path("linkResolution").asText()).isEqualTo("RESOLVED");
        JsonNode matched = orderPay.path("matchedTransitions");
        assertThat(matched.isArray()).isTrue();
        assertThat(matched.size()).isLessThanOrEqualTo(transitionCount);
        assertThat(matched.size()).isGreaterThan(0);
        assertThat(matched.get(0).path("event").asText())
                .contains("OrderEvent.PAY");
    }

    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()
                .orElseThrow(() -> new IllegalStateException("chain not found for " + endpointName));
    }

    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;
    }
}
