package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
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 java.util.Map;

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

class PathBindingEvaluatorTest {

    @Test
    void shouldPruneImpossibleSwitchDispatchArms(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                private final CommandGateway gateway = new CommandGateway();
                public void pay() { gateway.executeViaMapper("order.pay"); }
            }
            enum DomainCommand { ORDER_PAY, ORDER_SHIP }
            enum OrderEvent { PAY, SHIP }
            class CommandGateway {
                private final StringCommandMapper mapper = new StringCommandMapper();
                private final CentralDispatcher central = new CentralDispatcher();
                void executeViaMapper(String commandKey) {
                    DomainCommand command = mapper.fromString(commandKey);
                    central.dispatch(command);
                }
            }
            class StringCommandMapper {
                DomainCommand fromString(String commandKey) {
                    return switch (commandKey) {
                        case "order.pay" -> DomainCommand.ORDER_PAY;
                        case "order.ship" -> DomainCommand.ORDER_SHIP;
                        default -> throw new IllegalArgumentException(commandKey);
                    };
                }
            }
            class CentralDispatcher {
                void dispatch(DomainCommand command) {
                    switch (command) {
                        case ORDER_PAY -> orderPay();
                        case ORDER_SHIP -> orderShip();
                    }
                }
                void orderPay() { fire(OrderEvent.PAY); }
                void orderShip() { fire(OrderEvent.SHIP); }
                void fire(OrderEvent event) {}
            }
            """;
        Files.writeString(tempDir.resolve("App.java"), source);

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

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        Map<String, List<CallEdge>> graph = engine.buildCallGraph();

        CallGraphPathFinder pathFinder = new CallGraphPathFinder(context);
        PathBindingEvaluator evaluator = new PathBindingEvaluator(
                context, engine.variableTracer, context.getConstantResolver(), engine.typeResolver);

        List<List<String>> rawPaths = pathFinder.findAllPaths(
                "com.example.ApiController.pay",
                "com.example.CentralDispatcher.fire",
                graph,
                new java.util.HashSet<>());
        assertThat(rawPaths).hasSize(2);

        List<List<String>> constrainedPaths = pathFinder.findAllPaths(
                "com.example.ApiController.pay",
                "com.example.CentralDispatcher.fire",
                graph,
                new java.util.HashSet<>(),
                evaluator,
                Map.of());
        assertThat(constrainedPaths).hasSize(1);
        assertThat(constrainedPaths.get(0)).contains("com.example.CentralDispatcher.orderPay");

        List<CallEdge> dispatchEdges = graph.get("com.example.CentralDispatcher.dispatch");
        assertThat(dispatchEdges.stream().map(CallEdge::getConstraint).filter(c -> c != null && !c.isBlank()))
                .isNotEmpty();

        boolean payPathCompatible = evaluator.isPathCompatible(rawPaths.get(0), graph, pathFinder);
        Map<String, String> payBindings = evaluator.traceBindings(rawPaths.get(0), graph, pathFinder);
        assertThat(payBindings)
                .as("pay path bindings, compatible=%s", payPathCompatible)
                .containsEntry("command", "com.example.DomainCommand.ORDER_PAY");

        EntryPoint entry = EntryPoint.builder().className("com.example.ApiController").methodName("pay").build();
        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.CentralDispatcher")
                .methodName("fire")
                .event("event")
                .build();

        List<click.kamil.springstatemachineexporter.analysis.model.CallChain> chains =
                engine.findChains(List.of(entry), List.of(trigger));

        assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getMethodChain()).contains("com.example.CentralDispatcher.orderPay");
    }
}
