package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Path;
import java.util.List;

import static click.kamil.springstatemachineexporter.analysis.service.CentralDispatcherTestSupport.EngineKind;
import static click.kamil.springstatemachineexporter.analysis.service.CentralDispatcherTestSupport.*;
import static org.assertj.core.api.Assertions.assertThat;

/**
 * Ensures the production {@link JdtCallGraphEngine} resolves single-dispatcher endpoints the same
 * way as {@link HeuristicCallGraphEngine} for Tier-1 central-dispatcher patterns.
 */
class JdtCentralDispatcherParityTest {

    private static final String CONTROLLER = "com.example.ApiController";
    private static final String STATE_MACHINE = "com.example.StateMachine";
    private static final String MACHINE_CONFIG = "com.example.OrderStateMachineConfig";

    @Test
    void jdtShouldMatchHeuristicForFieldWiredRoutingHelper(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CommandGateway gateway;
                public void pay() { gateway.routeAndPay(); }
                public void ship() { gateway.routeAndShip(); }
            }
            class CommandGateway {
                OrderRoutingHelper routingHelper;
                CentralDispatcher dispatcher;
                void routeAndPay() { dispatcher.dispatch("ORDER", routingHelper.payAction()); }
                void routeAndShip() { dispatcher.dispatch("ORDER", routingHelper.shipAction()); }
            }
            class OrderRoutingHelper {
                String payAction() { return "PAY"; }
                String shipAction() { return "SHIP"; }
            }
            class CentralDispatcher {
                void dispatch(String domain, String action) {
                    StateMachine sm = new StateMachine();
                    sm.sendEvent(OrderEvent.valueOf(action));
                }
            }
            enum OrderEvent { PAY, SHIP }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain heuristicPay = resolveChain(context, CONTROLLER, "pay", STATE_MACHINE, EngineKind.HEURISTIC);
        CallChain jdtPay = resolveChain(context, CONTROLLER, "pay", STATE_MACHINE, EngineKind.JDT);
        CallChain heuristicShip = resolveChain(context, CONTROLLER, "ship", STATE_MACHINE, EngineKind.HEURISTIC);
        CallChain jdtShip = resolveChain(context, CONTROLLER, "ship", STATE_MACHINE, EngineKind.JDT);

        assertThat(jdtPay.getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrderElementsOf(heuristicPay.getTriggerPoint().getPolymorphicEvents());
        assertThat(jdtShip.getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrderElementsOf(heuristicShip.getTriggerPoint().getPolymorphicEvents());

        MatchedTransition heuristicLink = linkEndpointToTransition(heuristicPay, MACHINE_CONFIG,
                transition("NEW", "PAID", "OrderEvent.PAY"),
                transition("PAID", "SHIPPED", "OrderEvent.SHIP"));
        MatchedTransition jdtLink = linkEndpointToTransition(jdtPay, MACHINE_CONFIG,
                transition("NEW", "PAID", "OrderEvent.PAY"),
                transition("PAID", "SHIPPED", "OrderEvent.SHIP"));
        assertThat(jdtLink.getEvent()).isEqualTo(heuristicLink.getEvent());
    }

    @Test
    void jdtShouldMatchHeuristicForStaticRoutingHelper(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CommandGateway gateway;
                public void pay() { gateway.routeAndPay(); }
                public void ship() { gateway.routeAndShip(); }
            }
            class CommandGateway {
                CentralDispatcher dispatcher;
                void routeAndPay() { dispatcher.dispatch("ORDER", OrderRoutingHelper.payAction()); }
                void routeAndShip() { dispatcher.dispatch("ORDER", OrderRoutingHelper.shipAction()); }
            }
            class OrderRoutingHelper {
                static String payAction() { return "PAY"; }
                static String shipAction() { return "SHIP"; }
            }
            class CentralDispatcher {
                void dispatch(String domain, String action) {
                    StateMachine sm = new StateMachine();
                    sm.sendEvent(OrderEvent.valueOf(action));
                }
            }
            enum OrderEvent { PAY, SHIP }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain heuristicPay = resolveChain(context, CONTROLLER, "pay", STATE_MACHINE, EngineKind.HEURISTIC);
        CallChain jdtPay = resolveChain(context, CONTROLLER, "pay", STATE_MACHINE, EngineKind.JDT);
        CallChain heuristicShip = resolveChain(context, CONTROLLER, "ship", STATE_MACHINE, EngineKind.HEURISTIC);
        CallChain jdtShip = resolveChain(context, CONTROLLER, "ship", STATE_MACHINE, EngineKind.JDT);

        assertThat(jdtPay.getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrderElementsOf(heuristicPay.getTriggerPoint().getPolymorphicEvents());
        assertThat(jdtShip.getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrderElementsOf(heuristicShip.getTriggerPoint().getPolymorphicEvents());
    }

    @Test
    void jdtShouldMatchHeuristicForPublicDtoFieldAccess(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CommandGateway gateway;
                public void submit() { gateway.submit(new OrderRequest(OrderEvent.SUBMIT)); }
            }
            class CommandGateway {
                void submit(OrderRequest req) {
                    StateMachine sm = new StateMachine();
                    sm.sendEvent(req.type);
                }
            }
            class OrderRequest {
                public final OrderEvent type;
                OrderRequest(OrderEvent type) { this.type = type; }
            }
            enum OrderEvent { SUBMIT, PAY }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain heuristic = resolveChain(context, CONTROLLER, "submit", STATE_MACHINE, EngineKind.HEURISTIC);
        CallChain jdt = resolveChain(context, CONTROLLER, "submit", STATE_MACHINE, EngineKind.JDT);

        assertThat(jdt.getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrderElementsOf(heuristic.getTriggerPoint().getPolymorphicEvents());
    }

    @Test
    void jdtShouldMatchHeuristicForRecordComponentAccess(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CommandGateway gateway;
                public void submit() { gateway.submit(new OrderRequest(OrderEvent.SUBMIT)); }
            }
            class CommandGateway {
                void submit(OrderRequest req) {
                    StateMachine sm = new StateMachine();
                    sm.sendEvent(req.type());
                }
            }
            record OrderRequest(OrderEvent type) {}
            enum OrderEvent { SUBMIT, PAY }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain heuristic = resolveChain(context, CONTROLLER, "submit", STATE_MACHINE, EngineKind.HEURISTIC);
        CallChain jdt = resolveChain(context, CONTROLLER, "submit", STATE_MACHINE, EngineKind.JDT);

        assertThat(jdt.getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrderElementsOf(heuristic.getTriggerPoint().getPolymorphicEvents());
    }

    @Test
    void jdtShouldMatchHeuristicForDtoCrossHop(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CommandGateway gateway;
                public void submit() { gateway.submit(new OrderRequest(OrderEvent.SUBMIT)); }
            }
            class CommandGateway {
                void submit(OrderRequest req) {
                    StateMachine sm = new StateMachine();
                    sm.sendEvent(req.getType());
                }
            }
            class OrderRequest {
                private final OrderEvent type;
                OrderRequest(OrderEvent type) { this.type = type; }
                OrderEvent getType() { return type; }
            }
            enum OrderEvent { SUBMIT, PAY }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain heuristic = resolveChain(context, CONTROLLER, "submit", STATE_MACHINE, EngineKind.HEURISTIC);
        CallChain jdt = resolveChain(context, CONTROLLER, "submit", STATE_MACHINE, EngineKind.JDT);

        assertThat(jdt.getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrderElementsOf(heuristic.getTriggerPoint().getPolymorphicEvents());

        MatchedTransition heuristicLink = linkEndpointToTransition(heuristic, MACHINE_CONFIG,
                transition("DRAFT", "SUBMITTED", "OrderEvent.SUBMIT"),
                transition("SUBMITTED", "PAID", "OrderEvent.PAY"));
        MatchedTransition jdtLink = linkEndpointToTransition(jdt, MACHINE_CONFIG,
                transition("DRAFT", "SUBMITTED", "OrderEvent.SUBMIT"),
                transition("SUBMITTED", "PAID", "OrderEvent.PAY"));
        assertThat(jdtLink.getEvent()).isEqualTo(heuristicLink.getEvent());
    }

    @Test
    void jdtShouldMatchHeuristicForSingleDispatcherNamedMethods(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CentralDispatcher dispatcher;
                public void pay() { dispatcher.orderPay(); }
                public void ship() { dispatcher.orderShip(); }
            }
            class CentralDispatcher {
                public void orderPay() {
                    StateMachine sm = new StateMachine();
                    sm.sendEvent(OrderEvent.PAY);
                }
                public void orderShip() {
                    StateMachine sm = new StateMachine();
                    sm.sendEvent(OrderEvent.SHIP);
                }
            }
            enum OrderEvent { PAY, SHIP }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        for (String method : List.of("pay", "ship")) {
            CallChain heuristic = resolveChain(context, CONTROLLER, method, STATE_MACHINE, EngineKind.HEURISTIC);
            CallChain jdt = resolveChain(context, CONTROLLER, method, STATE_MACHINE, EngineKind.JDT);
            assertThat(jdt.getTriggerPoint().getPolymorphicEvents())
                    .containsExactlyInAnyOrderElementsOf(heuristic.getTriggerPoint().getPolymorphicEvents());
        }
    }
}
