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;

/**
 * String-typed machines with an abstract ConfigurerAdapter base must keep package-proven
 * JMS/REST chains. Linker must not stamp non-concrete {@code String} type FQNs onto triggers
 * and then fail-closed as ambiguous shared generics.
 */
class MavenOrderStringMachineLinkageRegressionTest {

    @Test
    void coreModuleExportRetainsJmsAndRestResolvedChains(@TempDir Path tempDir) throws Exception {
        Path coreModule = findProjectRoot().resolve("state_machines/maven_multi_module/core-module");
        ExportService exportService = new ExportService(List.of(new JsonExporter()));
        exportService.runExporter(coreModule, 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, "MavenOrderStateMachine", new ObjectMapper());
        JsonNode chains = json.path("metadata").path("callChains");
        JsonNode entryPoints = json.path("metadata").path("entryPoints");

        assertThat(chains.isArray()).isTrue();
        assertThat(chains.size()).isGreaterThanOrEqualTo(2);
        assertThat(entryPoints.isArray()).isTrue();
        assertThat(entryPoints.size()).isGreaterThanOrEqualTo(2);

        JsonNode jms = findChainByEndpoint(json, "JMS: order.queue");
        assertThat(jms.path("linkResolution").asText()).isEqualTo("RESOLVED");
        assertThat(jms.path("triggerPoint").path("event").asText()).contains("ORDER_EVENT");
        assertThat(jms.path("matchedTransitions")).isNotEmpty();

        JsonNode rest = findChainByEndpoint(json, "POST /maven/orders/submit");
        assertThat(rest.path("linkResolution").asText()).isEqualTo("RESOLVED");
        assertThat(rest.path("triggerPoint").path("event").asText()).contains("SUBMIT");
        assertThat(rest.path("matchedTransitions")).isNotEmpty();

        assertThat(StreamSupport.stream(entryPoints.spliterator(), false)
                .anyMatch(ep -> "JMS".equals(ep.path("type").asText()))).isTrue();
    }

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