package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.LambdaExpression;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;

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

class CallSiteMatcherTest {

    @Test
    void shouldMatchCallSiteArgumentToSpecificEdgeWhenMultipleCallsExist(@TempDir Path tempDir) throws IOException {
        Files.writeString(tempDir.resolve("App.java"), """
                package com.example;
                import java.util.function.Supplier;
                public enum OrderCommand { PAY, SHIP }
                public class OrderController {
                    private final OrderService service;
                    public OrderController(OrderService service) { this.service = service; }
                    public void both() {
                        service.fireEvent(() -> OrderCommand.PAY);
                        service.fireEvent(() -> OrderCommand.SHIP);
                    }
                }
                class OrderService {
                    void fireEvent(Supplier<OrderCommand> provider) {}
                }
                """);

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.scan(tempDir);

        CallEdge payEdge = new CallEdge(
                "com.example.OrderService.fireEvent",
                List.of("() -> OrderCommand.PAY"));
        CallEdge shipEdge = new CallEdge(
                "com.example.OrderService.fireEvent",
                List.of("() -> OrderCommand.SHIP"));

        MethodInvocation payInvocation = CallSiteMatcher.findMatchingInvocation(
                "com.example.OrderController.both",
                "com.example.OrderService.fireEvent",
                payEdge,
                context);
        MethodInvocation shipInvocation = CallSiteMatcher.findMatchingInvocation(
                "com.example.OrderController.both",
                "com.example.OrderService.fireEvent",
                shipEdge,
                context);

        assertThat(payInvocation).isNotNull();
        assertThat(shipInvocation).isNotNull();
        assertThat(payInvocation).isNotSameAs(shipInvocation);
        assertThat(payInvocation.arguments().get(0)).isInstanceOf(LambdaExpression.class);
        assertThat(shipInvocation.arguments().get(0)).isInstanceOf(LambdaExpression.class);

        VariableTracer tracer = new VariableTracer(context, context.getConstantResolver());
        tracer.setTypeResolver(new TypeResolver(context));
        tracer.setConstantExtractor(new ConstantExtractor(context, context.getConstantResolver(), null));

        assertThat(tracer.resolveConstantsFromCallSiteArgument(
                "com.example.OrderController.both",
                "com.example.OrderService.fireEvent",
                0,
                payEdge)).containsExactly("OrderCommand.PAY");
        assertThat(tracer.resolveConstantsFromCallSiteArgument(
                "com.example.OrderController.both",
                "com.example.OrderService.fireEvent",
                0,
                shipEdge)).containsExactly("OrderCommand.SHIP");
    }

    @Test
    void shouldSelectEdgeUsingPathBindings(@TempDir Path tempDir) throws IOException {
        CallEdge payEdge = new CallEdge(
                "com.example.OrderService.fireEvent",
                List.of("() -> OrderCommand.PAY"));
        CallEdge shipEdge = new CallEdge(
                "com.example.OrderService.fireEvent",
                List.of("() -> OrderCommand.SHIP"));

        CallEdge selected = CallSiteMatcher.selectEdge(
                "com.example.OrderController.both",
                "com.example.OrderService.fireEvent",
                List.of(payEdge, shipEdge),
                Map.of("event", "OrderCommand.SHIP"));

        assertThat(selected).isSameAs(shipEdge);
    }
}
