package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.ProjectModuleGraph;
import click.kamil.springstatemachineexporter.analysis.resolver.SiblingDependencyResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
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.Map;
import java.util.Set;
import java.util.Collections;

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

/**
 * Path binding expansion must resolve helpers declared in sibling modules without executing the build.
 */
class MultiModulePathBindingExpanderTest {

    @Test
    void shouldExpandEventUsingHelperFromSiblingModule(@TempDir Path tempDir) throws Exception {
        Path root = tempDir.resolve("order-system");
        Files.createDirectories(root);
        Files.writeString(root.resolve("settings.gradle"), "include 'api-module', 'core-module'");

        Path apiModule = root.resolve("api-module");
        Files.createDirectories(apiModule.resolve("src/main/java/com/example/util"));
        Files.writeString(apiModule.resolve("build.gradle"), "");
        Files.writeString(apiModule.resolve("src/main/java/com/example/util/EventNormalizer.java"), """
                package com.example.util;
                public class EventNormalizer {
                    public static String normalize(String raw) {
                        return raw.toUpperCase();
                    }
                }
                """);

        Path coreModule = root.resolve("core-module");
        Files.createDirectories(coreModule.resolve("src/main/java/com/example"));
        Files.writeString(coreModule.resolve("build.gradle"),
                "dependencies { implementation project(':api-module') }");
        Files.writeString(coreModule.resolve("src/main/java/com/example/OrderGateway.java"), """
                package com.example;
                import com.example.util.EventNormalizer;
                public class OrderController {
                    OrderGateway gateway;
                    public void transition(String event) {
                        gateway.trigger(event);
                    }
                }
                class OrderGateway {
                    void trigger(String event) {
                        OrderEvent.valueOf(EventNormalizer.normalize(event));
                    }
                }
                enum OrderEvent { PAY, SHIP }
                """);

        SiblingDependencyResolver siblingResolver = new SiblingDependencyResolver();
        ProjectModuleGraph graph = siblingResolver.analyzeProject(coreModule);
        Set<Path> scanPaths = graph.resolveScanPaths(coreModule, false);

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.scan(scanPaths, Collections.emptySet());

        assertThat(context.getTypeDeclaration("com.example.util.EventNormalizer")).isNotNull();

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        Map<String, List<click.kamil.springstatemachineexporter.analysis.model.CallEdge>> callGraph =
                engine.buildCallGraph();

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("transition")
                .name("POST /api/order/{event}")
                .parameters(List.of(EntryPoint.Parameter.builder()
                        .name("event")
                        .type("String")
                        .annotations(List.of("PathVariable"))
                        .build()))
                .build();

        List<Map<String, String>> variants =
                EntryPointBindingExpander.expandPathVariableBindings(entryPoint, context, callGraph);

        assertThat(variants).extracting(map -> map.get("event"))
                .containsExactlyInAnyOrder("PAY", "SHIP");
        assertThat(EntryPointBindingExpander.withResolvedPath(
                entryPoint, Map.of("event", "PAY")).getName())
                .isEqualTo("POST /api/order/PAY");
    }

    @Test
    void shouldExpandEventUsingHelperFromSiblingMavenModule(@TempDir Path tempDir) throws Exception {
        Path root = tempDir.resolve("order-maven");
        Files.createDirectories(root.resolve(".git"));
        Files.writeString(root.resolve("pom.xml"), """
                <project><groupId>com.example</groupId><artifactId>order-maven</artifactId></project>
                """);

        Path apiModule = root.resolve("api-module");
        Files.createDirectories(apiModule.resolve("src/main/java/com/example/util"));
        Files.writeString(apiModule.resolve("pom.xml"), """
                <project><groupId>com.example</groupId><artifactId>api-module</artifactId></project>
                """);
        Files.writeString(apiModule.resolve("src/main/java/com/example/util/EventNormalizer.java"), """
                package com.example.util;
                public class EventNormalizer {
                    public static String normalize(String raw) {
                        return raw.toUpperCase();
                    }
                }
                """);

        Path coreModule = root.resolve("core-module");
        Files.createDirectories(coreModule.resolve("src/main/java/com/example"));
        Files.writeString(coreModule.resolve("pom.xml"), """
                <project>
                  <groupId>com.example</groupId>
                  <artifactId>core-module</artifactId>
                  <dependencies>
                    <dependency>
                      <groupId>com.example</groupId>
                      <artifactId>api-module</artifactId>
                      <version>1.0</version>
                    </dependency>
                  </dependencies>
                </project>
                """);
        Files.writeString(coreModule.resolve("src/main/java/com/example/OrderGateway.java"), """
                package com.example;
                import com.example.util.EventNormalizer;
                public class OrderController {
                    OrderGateway gateway;
                    public void transition(String event) {
                        gateway.trigger(event);
                    }
                }
                class OrderGateway {
                    void trigger(String event) {
                        OrderEvent.valueOf(EventNormalizer.normalize(event));
                    }
                }
                enum OrderEvent { PAY, SHIP }
                """);

        SiblingDependencyResolver siblingResolver = new SiblingDependencyResolver();
        ProjectModuleGraph graph = siblingResolver.analyzeProject(coreModule);
        Set<Path> scanPaths = graph.resolveScanPaths(coreModule, false);

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.scan(scanPaths, Collections.emptySet());

        assertThat(context.getTypeDeclaration("com.example.util.EventNormalizer")).isNotNull();

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        Map<String, List<click.kamil.springstatemachineexporter.analysis.model.CallEdge>> callGraph =
                engine.buildCallGraph();

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("transition")
                .name("POST /api/order/{event}")
                .parameters(List.of(EntryPoint.Parameter.builder()
                        .name("event")
                        .type("String")
                        .annotations(List.of("PathVariable"))
                        .build()))
                .build();

        List<Map<String, String>> variants =
                EntryPointBindingExpander.expandPathVariableBindings(entryPoint, context, callGraph);

        assertThat(variants).extracting(map -> map.get("event"))
                .containsExactlyInAnyOrder("PAY", "SHIP");
    }
}
