package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
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 static org.assertj.core.api.Assertions.assertThat;

/**
 * Implicit-{@code this} helpers that return enum constants must not be peeled by
 * {@code unwrapConverterMethods} — otherwise ConstantExtractor never widens to subclass overrides.
 */
class ImplicitThisHelperEventResolutionTest {

    @Test
    void shouldResolveEventFromImplicitThisHelperOverrides(@TempDir Path tempDir) throws Exception {
        Path javaRoot = tempDir.resolve("src/main/java/com/example");
        Files.createDirectories(javaRoot);
        Files.writeString(tempDir.resolve("build.gradle"), "plugins { id 'java' }");
        Files.writeString(javaRoot.resolve("OrderEvent.java"), """
                package com.example;
                public enum OrderEvent { PAY, SHIP, LOG }
                """);
        Files.writeString(javaRoot.resolve("StateMachine.java"), """
                package com.example;
                public class StateMachine {
                    void sendEvent(OrderEvent event) {}
                }
                """);
        Files.writeString(javaRoot.resolve("AbstractDispatcher.java"), """
                package com.example;
                public abstract class AbstractDispatcher {
                    private final StateMachine sm = new StateMachine();
                    protected abstract OrderEvent toEvent(String message);
                    public void handle(String message) {
                        sm.sendEvent(toEvent(message));
                    }
                }
                """);
        Files.writeString(javaRoot.resolve("PayDispatcher.java"), """
                package com.example;
                public class PayDispatcher extends AbstractDispatcher {
                    @Override
                    protected OrderEvent toEvent(String message) {
                        return OrderEvent.PAY;
                    }
                }
                """);
        Files.writeString(javaRoot.resolve("ShipDispatcher.java"), """
                package com.example;
                public class ShipDispatcher extends AbstractDispatcher {
                    @Override
                    protected OrderEvent toEvent(String message) {
                        return OrderEvent.SHIP;
                    }
                }
                """);
        Files.writeString(javaRoot.resolve("OrderController.java"), """
                package com.example;
                public class OrderController {
                    private final AbstractDispatcher dispatcher;
                    public OrderController(AbstractDispatcher dispatcher) {
                        this.dispatcher = dispatcher;
                    }
                    public void onCommand(String message) {
                        dispatcher.handle(message);
                    }
                }
                """);

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.resolve("src/main/java").toString()));
        context.scan(tempDir);

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        List<CallChain> chains = engine.findChains(
                List.of(EntryPoint.builder()
                        .type(EntryPoint.Type.REST)
                        .name("POST /commands")
                        .className("com.example.OrderController")
                        .methodName("onCommand")
                        .parameters(List.of(EntryPoint.Parameter.builder()
                                .name("message")
                                .type("String")
                                .annotations(List.of("PathVariable"))
                                .build()))
                        .build()),
                List.of(TriggerPoint.builder()
                        .className("com.example.StateMachine")
                        .methodName("sendEvent")
                        .event("event")
                        .build()));

        assertThat(chains).isNotEmpty();
        List<String> poly = chains.stream()
                .flatMap(chain -> {
                    List<String> events = chain.getTriggerPoint().getPolymorphicEvents();
                    return events == null ? java.util.stream.Stream.<String>empty() : events.stream();
                })
                .distinct()
                .toList();

        assertThat(poly)
                .as("toEvent(message) must stay intact so override returns are collected")
                .anyMatch(e -> e.endsWith(".PAY") || e.equals("PAY") || e.endsWith("OrderEvent.PAY"))
                .anyMatch(e -> e.endsWith(".SHIP") || e.equals("SHIP") || e.endsWith("OrderEvent.SHIP"));
        assertThat(poly)
                .as("must not collapse to the raw message parameter after destructive unwrap")
                .noneMatch(e -> "message".equals(e));
    }

    @Test
    void shouldStillPeelValueOfForParameterTracing(@TempDir Path tempDir) throws Exception {
        Path javaRoot = tempDir.resolve("src/main/java/com/example");
        Files.createDirectories(javaRoot);
        Files.writeString(tempDir.resolve("build.gradle"), "plugins { id 'java' }");
        Files.writeString(javaRoot.resolve("OrderEvent.java"), """
                package com.example;
                public enum OrderEvent { PAY, SHIP }
                """);
        Files.writeString(javaRoot.resolve("StateMachine.java"), """
                package com.example;
                public class StateMachine {
                    void sendEvent(OrderEvent event) {}
                }
                """);
        Files.writeString(javaRoot.resolve("OrderController.java"), """
                package com.example;
                public class OrderController {
                    private final StateMachine sm = new StateMachine();
                    public void onCommand(String eventStr) {
                        sm.sendEvent(OrderEvent.valueOf(eventStr));
                    }
                }
                """);

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.resolve("src/main/java").toString()));
        context.scan(tempDir);

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        List<CallChain> chains = engine.findChains(
                List.of(EntryPoint.builder()
                        .type(EntryPoint.Type.REST)
                        .name("POST /commands/{eventStr}")
                        .className("com.example.OrderController")
                        .methodName("onCommand")
                        .parameters(List.of(EntryPoint.Parameter.builder()
                                .name("eventStr")
                                .type("String")
                                .annotations(List.of("PathVariable"))
                                .build()))
                        .build()),
                List.of(TriggerPoint.builder()
                        .className("com.example.StateMachine")
                        .methodName("sendEvent")
                        .event("event")
                        .build()));

        assertThat(chains).isNotEmpty();
        // valueOf peel must still allow tracing back to the path variable name.
        assertThat(chains.stream()
                .map(c -> c.getTriggerPoint().getEvent())
                .anyMatch(e -> e != null && (e.contains("eventStr") || e.contains("valueOf"))))
                .isTrue();
    }
}
