package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.enricher.TransitionLinkerEnricher;
import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
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.*;
import static org.assertj.core.api.Assertions.assertThat;

/**
 * Readable unit tests for central-dispatcher endpoint resolution.
 *
 * <p>Each test embeds a miniature codebase as a string so humans and agents can see the full
 * REST → dispatcher → sendEvent pipeline without opening another module.
 *
 * <p>Cross-hop routing helpers and DTO constructor→getter propagation are covered by Tier 1 tests below.
 */
class CentralDispatcherResolutionTest {

    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";

    /**
     * REST endpoint passes two string literals into a shared dispatcher.
     * The dispatcher selects the machine branch, then resolves the enum constant.
     *
     * <pre>
     * payOrder()  → dispatch("ORDER", "PAY")  → OrderEvent.PAY
     * shipOrder() → dispatch("ORDER", "SHIP") → OrderEvent.SHIP
     * </pre>
     */
    @Test
    void twoFieldCentralDispatcherShouldResolveDistinctEventsPerEndpoint(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CentralDispatcher dispatcher;
                public void payOrder() { dispatcher.dispatch("ORDER", "PAY"); }
                public void shipOrder() { dispatcher.dispatch("ORDER", "SHIP"); }
            }
            class CentralDispatcher {
                public void dispatch(String domain, String action) {
                    StateMachine sm = new StateMachine();
                    if ("ORDER".equals(domain)) {
                        OrderEvent ev = OrderEvent.valueOf(action);
                        sm.sendEvent(ev);
                    }
                }
            }
            enum OrderEvent { PAY, SHIP, CANCEL }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain payChain = resolveChain(context, CONTROLLER, "payOrder", STATE_MACHINE);
        CallChain shipChain = resolveChain(context, CONTROLLER, "shipOrder", STATE_MACHINE);

        assertPolyEvents(payChain, "OrderEvent.PAY");
        assertPolyEvents(shipChain, "OrderEvent.SHIP");

        MatchedTransition pay = linkEndpointToTransition(
                payChain,
                MACHINE_CONFIG,
                transition("NEW", "PAID", "OrderEvent.PAY"),
                transition("PAID", "SHIPPED", "OrderEvent.SHIP"));
        MatchedTransition ship = linkEndpointToTransition(
                shipChain,
                MACHINE_CONFIG,
                transition("NEW", "PAID", "OrderEvent.PAY"),
                transition("PAID", "SHIPPED", "OrderEvent.SHIP"));

        assertLinkedEvent(pay, "OrderEvent.PAY");
        assertLinkedEvent(ship, "OrderEvent.SHIP");
    }

    /**
     * Three request dimensions (domain, action, version) are passed as literals and folded
     * together by a routing helper before the central dispatcher fires the SM event.
     *
     * <pre>
     * payV2()  → dispatch("ORDER", "PAY", "v2")  → OrderEvent.valueOf(action)
     * shipV2() → dispatch("ORDER", "SHIP", "v2") → OrderEvent.valueOf(action)
     * </pre>
     *
     * <p>The third field is forwarded for API shape parity; routing uses domain + action literals.
     */
    @Test
    void threeFieldCentralDispatcherShouldResolveFromCombinedRoutingKey(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CentralDispatcher dispatcher;
                public void payV2() { dispatcher.dispatch("ORDER", "PAY", "v2"); }
                public void shipV2() { dispatcher.dispatch("ORDER", "SHIP", "v2"); }
            }
            class CentralDispatcher {
                public void dispatch(String domain, String action, String version) {
                    StateMachine sm = new StateMachine();
                    if ("ORDER".equals(domain)) {
                        OrderEvent ev = OrderEvent.valueOf(action);
                        sm.sendEvent(ev);
                    }
                }
            }
            enum OrderEvent { PAY, SHIP, CANCEL }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain payChain = resolveChain(context, CONTROLLER, "payV2", STATE_MACHINE);
        CallChain shipChain = resolveChain(context, CONTROLLER, "shipV2", STATE_MACHINE);

        assertPolyEvents(payChain, "OrderEvent.PAY");
        assertPolyEvents(shipChain, "OrderEvent.SHIP");

        MatchedTransition pay = linkEndpointToTransition(
                payChain, MACHINE_CONFIG,
                transition("NEW", "PAID", "OrderEvent.PAY"),
                transition("PAID", "SHIPPED", "OrderEvent.SHIP"));
        MatchedTransition ship = linkEndpointToTransition(
                shipChain, MACHINE_CONFIG,
                transition("NEW", "PAID", "OrderEvent.PAY"),
                transition("PAID", "SHIPPED", "OrderEvent.SHIP"));

        assertLinkedEvent(pay, "OrderEvent.PAY");
        assertLinkedEvent(ship, "OrderEvent.SHIP");
    }

    /**
     * Gateway receives three separate fields from the endpoint and forwards them to the
     * central dispatcher (same routing core as the three-field test, without DTO indirection).
     */
    @Test
    void gatewayShouldForwardThreeRequestFieldsToCentralDispatcher(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                Gateway gateway;
                public void submit() {
                    gateway.handle("ORDER", "SUBMIT", "v2");
                }
                public void approve() {
                    gateway.handle("ORDER", "APPROVE", "v2");
                }
            }
            class Gateway {
                CentralDispatcher dispatcher;
                void handle(String domain, String action, String version) {
                    dispatcher.dispatch(domain, action, version);
                }
            }
            class CentralDispatcher {
                public void dispatch(String domain, String action, String version) {
                    StateMachine sm = new StateMachine();
                    if ("ORDER".equals(domain)) {
                        OrderEvent ev = OrderEvent.valueOf(action);
                        sm.sendEvent(ev);
                    }
                }
            }
            enum OrderEvent { SUBMIT, APPROVE, REJECT }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain submitChain = resolveChain(context, CONTROLLER, "submit", STATE_MACHINE);
        CallChain approveChain = resolveChain(context, CONTROLLER, "approve", STATE_MACHINE);

        assertPolyEvents(submitChain, "OrderEvent.SUBMIT");
        assertPolyEvents(approveChain, "OrderEvent.APPROVE");

        assertLinkedEvent(
                linkEndpointToTransition(
                        submitChain, MACHINE_CONFIG,
                        transition("DRAFT", "SUBMITTED", "OrderEvent.SUBMIT"),
                        transition("SUBMITTED", "APPROVED", "OrderEvent.APPROVE")),
                "OrderEvent.SUBMIT");
        assertLinkedEvent(
                linkEndpointToTransition(
                        approveChain, MACHINE_CONFIG,
                        transition("DRAFT", "SUBMITTED", "OrderEvent.SUBMIT"),
                        transition("SUBMITTED", "APPROVED", "OrderEvent.APPROVE")),
                "OrderEvent.APPROVE");
    }

    /**
     * Two concrete state-machine configs share one central dispatcher class.
     * Each endpoint must link only to transitions from its own config, not the sibling machine.
     */
    @Test
    void sharedCentralDispatcherShouldRouteEndpointsToCorrectMachineConfig(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class OrderApi {
                SharedDispatcher dispatcher;
                public void pay() { dispatcher.payOrder(); }
            }
            public class ShipmentApi {
                SharedDispatcher dispatcher;
                public void dispatchShipment() { dispatcher.dispatchShipment(); }
            }
            class SharedDispatcher {
                public void payOrder() {
                    OrderMachine sm = new OrderMachine();
                    sm.sendEvent(OrderEvent.PAY);
                }
                public void dispatchShipment() {
                    ShipmentMachine sm = new ShipmentMachine();
                    sm.sendEvent(ShipmentEvent.DISPATCH);
                }
            }
            enum OrderEvent { PAY, SHIP }
            enum ShipmentEvent { DISPATCH, DELIVER }
            class OrderMachine { public void sendEvent(OrderEvent e) {} }
            class ShipmentMachine { public void sendEvent(ShipmentEvent e) {} }
            class OrderStateMachineConfig {}
            class ShipmentStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain orderChain = resolveChain(context, "com.example.OrderApi", "pay", "com.example.OrderMachine");
        CallChain shipmentChain = resolveChain(context, "com.example.ShipmentApi", "dispatchShipment", "com.example.ShipmentMachine");

        assertPolyEvents(orderChain, "OrderEvent.PAY");
        assertPolyEvents(shipmentChain, "ShipmentEvent.DISPATCH");

        MatchedTransition orderLink = linkEndpointToTransition(
                orderChain,
                "com.example.OrderStateMachineConfig",
                transition("NEW", "PAID", "OrderEvent.PAY"),
                transition("PAID", "SHIPPED", "OrderEvent.SHIP"));
        MatchedTransition shipmentLink = linkEndpointToTransition(
                shipmentChain,
                "com.example.ShipmentStateMachineConfig",
                transition("READY", "IN_TRANSIT", "ShipmentEvent.DISPATCH"),
                transition("IN_TRANSIT", "DELIVERED", "ShipmentEvent.DELIVER"));

        assertLinkedEvent(orderLink, "OrderEvent.PAY");
        assertLinkedEvent(shipmentLink, "ShipmentEvent.DISPATCH");

        // Negative check: order endpoint must not link against shipment transitions.
        AnalysisResult wrongMachine = AnalysisResult.builder()
                .name("com.example.ShipmentStateMachineConfig")
                .transitions(List.of(transition("READY", "IN_TRANSIT", "ShipmentEvent.DISPATCH")))
                .metadata(click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata.builder()
                        .callChains(List.of(orderChain))
                        .build())
                .build();
        new TransitionLinkerEnricher().enrich(wrongMachine, null, null);
        assertThat(wrongMachine.getMetadata().getCallChains().get(0).getMatchedTransitions()).isNull();
    }

    /**
     * Intermediate enum hop: endpoint literal → {@code DomainCommand} → concrete SM event.
     * Uses named dispatcher methods so each endpoint resolves to a single transition.
     */
    @Test
    void intermediateEnumCentralDispatcherShouldResolveSingleEventPerEndpoint(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CommandGateway gateway;
                public void pay() { gateway.payOrder(); }
                public void ship() { gateway.shipOrder(); }
            }
            enum DomainCommand { ORDER_PAY, ORDER_SHIP }
            enum OrderEvent { PAY, SHIP }
            class CommandGateway {
                CentralDispatcher central;
                void payOrder() { central.orderPay(); }
                void shipOrder() { central.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);
                }
            }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain payChain = resolveChain(context, CONTROLLER, "pay", STATE_MACHINE);
        CallChain shipChain = resolveChain(context, CONTROLLER, "ship", STATE_MACHINE);

        assertPolyEvents(payChain, "OrderEvent.PAY");
        assertPolyEvents(shipChain, "OrderEvent.SHIP");

        assertLinkedEvent(
                linkEndpointToTransition(payChain, MACHINE_CONFIG,
                        transition("NEW", "PAID", "OrderEvent.PAY"),
                        transition("PAID", "SHIPPED", "OrderEvent.SHIP")),
                "OrderEvent.PAY");
        assertLinkedEvent(
                linkEndpointToTransition(shipChain, MACHINE_CONFIG,
                        transition("NEW", "PAID", "OrderEvent.PAY"),
                        transition("PAID", "SHIPPED", "OrderEvent.SHIP")),
                "OrderEvent.SHIP");
    }

    /**
     * Tier 2 — REST → gateway → central dispatcher → {@code richEvent.getType()} → {@code sendEvent}.
     *
     * <pre>
     * pay()  → gateway.payWithRichEvent(new PayRichEvent()) → central.orderPay(e) → e.getType() → OrderEvent.PAY
     * ship() → gateway.shipWithRichEvent(new ShipRichEvent()) → central.orderShip(e) → e.getType() → OrderEvent.SHIP
     * </pre>
     */
    @Test
    void richEventCentralDispatcherShouldResolveDistinctEventsPerEndpoint(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CommandGateway gateway;
                public void pay() { gateway.payWithRichEvent(new PayRichEvent()); }
                public void ship() { gateway.shipWithRichEvent(new ShipRichEvent()); }
            }
            interface RichOrderEvent { OrderEvent getType(); }
            class PayRichEvent implements RichOrderEvent {
                public OrderEvent getType() { return OrderEvent.PAY; }
            }
            class ShipRichEvent implements RichOrderEvent {
                public OrderEvent getType() { return OrderEvent.SHIP; }
            }
            class CommandGateway {
                CentralDispatcher central;
                void payWithRichEvent(RichOrderEvent event) { central.orderPay(event); }
                void shipWithRichEvent(RichOrderEvent event) { central.orderShip(event); }
            }
            class CentralDispatcher {
                public void orderPay(RichOrderEvent event) {
                    StateMachine sm = new StateMachine();
                    sm.sendEvent(event.getType());
                }
                public void orderShip(RichOrderEvent event) {
                    StateMachine sm = new StateMachine();
                    sm.sendEvent(event.getType());
                }
            }
            enum OrderEvent { PAY, SHIP, CANCEL }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain payChain = resolveChain(context, CONTROLLER, "pay", STATE_MACHINE);
        CallChain shipChain = resolveChain(context, CONTROLLER, "ship", STATE_MACHINE);

        assertPolyEvents(payChain, "OrderEvent.PAY");
        assertPolyEvents(shipChain, "OrderEvent.SHIP");

        assertLinkedEvent(
                linkEndpointToTransition(payChain, MACHINE_CONFIG,
                        transition("NEW", "PAID", "OrderEvent.PAY"),
                        transition("PAID", "SHIPPED", "OrderEvent.SHIP")),
                "OrderEvent.PAY");
        assertLinkedEvent(
                linkEndpointToTransition(shipChain, MACHINE_CONFIG,
                        transition("NEW", "PAID", "OrderEvent.PAY"),
                        transition("PAID", "SHIPPED", "OrderEvent.SHIP")),
                "OrderEvent.SHIP");
    }

    /**
     * Tier 2 — chained accessor {@code wrapper.getEvent().getType()} through a central dispatcher.
     */
    @Test
    void chainedRichEventAccessorShouldResolveThroughCentralDispatcher(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CentralDispatcher dispatcher;
                public void pay(EventWrapper wrapper) {
                    dispatcher.fireOrderEvent(wrapper);
                }
            }
            class CentralDispatcher {
                public void fireOrderEvent(EventWrapper wrapper) {
                    StateMachine sm = new StateMachine();
                    sm.sendEvent(wrapper.getEvent().getType());
                }
            }
            class EventWrapper {
                public RichEvent getEvent() { return new RichEvent(); }
            }
            class RichEvent {
                public OrderEvent getType() { return OrderEvent.PAY; }
            }
            enum OrderEvent { PAY, SHIP }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain chain = resolveChain(context, CONTROLLER, "pay", STATE_MACHINE);
        assertPolyEvents(chain, "OrderEvent.PAY");
        assertLinkedEvent(
                linkEndpointToTransition(chain, MACHINE_CONFIG,
                        transition("NEW", "PAID", "OrderEvent.PAY"),
                        transition("PAID", "SHIPPED", "OrderEvent.SHIP")),
                "OrderEvent.PAY");
    }

    /**
     * Tier 1 — field-injected routing helper returns string literals consumed by central dispatcher.
     *
     * <pre>
     * pay()  → gateway.routeAndPay()  → dispatch("ORDER", routingHelper.payAction())  → OrderEvent.PAY
     * ship() → gateway.routeAndShip() → dispatch("ORDER", routingHelper.shipAction()) → OrderEvent.SHIP
     * </pre>
     */
    @Test
    void fieldWiredRoutingHelperShouldForwardLiteralsToCentralDispatcher(@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 payChain = resolveChain(context, CONTROLLER, "pay", STATE_MACHINE);
        CallChain shipChain = resolveChain(context, CONTROLLER, "ship", STATE_MACHINE);

        assertPolyEvents(payChain, "OrderEvent.PAY");
        assertPolyEvents(shipChain, "OrderEvent.SHIP");

        assertLinkedEvent(
                linkEndpointToTransition(payChain, MACHINE_CONFIG,
                        transition("NEW", "PAID", "OrderEvent.PAY"),
                        transition("PAID", "SHIPPED", "OrderEvent.SHIP")),
                "OrderEvent.PAY");
        assertLinkedEvent(
                linkEndpointToTransition(shipChain, MACHINE_CONFIG,
                        transition("NEW", "PAID", "OrderEvent.PAY"),
                        transition("PAID", "SHIPPED", "OrderEvent.SHIP")),
                "OrderEvent.SHIP");
    }

    @Test
    void staticRoutingHelperShouldForwardLiteralsToCentralDispatcher(@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 payChain = resolveChain(context, CONTROLLER, "pay", STATE_MACHINE);
        CallChain shipChain = resolveChain(context, CONTROLLER, "ship", STATE_MACHINE);

        assertPolyEvents(payChain, "OrderEvent.PAY");
        assertPolyEvents(shipChain, "OrderEvent.SHIP");

        assertLinkedEvent(
                linkEndpointToTransition(payChain, MACHINE_CONFIG,
                        transition("NEW", "PAID", "OrderEvent.PAY"),
                        transition("PAID", "SHIPPED", "OrderEvent.SHIP")),
                "OrderEvent.PAY");
        assertLinkedEvent(
                linkEndpointToTransition(shipChain, MACHINE_CONFIG,
                        transition("NEW", "PAID", "OrderEvent.PAY"),
                        transition("PAID", "SHIPPED", "OrderEvent.SHIP")),
                "OrderEvent.SHIP");
    }

    /**
     * Tier 1 — DTO field set via constructor in controller, read via getter in gateway.
     *
     * <pre>
     * submit() → gateway.submit(new OrderRequest(OrderEvent.SUBMIT)) → req.getType() → OrderEvent.SUBMIT
     * </pre>
     */
    @Test
    void dtoCrossHopFieldFlowShouldReachCentralDispatcher(@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 submitChain = resolveChain(context, CONTROLLER, "submit", STATE_MACHINE);

        assertPolyEvents(submitChain, "OrderEvent.SUBMIT");
        assertLinkedEvent(
                linkEndpointToTransition(submitChain, MACHINE_CONFIG,
                        transition("DRAFT", "SUBMITTED", "OrderEvent.SUBMIT"),
                        transition("SUBMITTED", "PAID", "OrderEvent.PAY")),
                "OrderEvent.SUBMIT");
    }

    /**
     * Chained field-backed trivial getters ({@code return this.field}) through a central dispatcher.
     */
    @Test
    void fieldBackedGetterChainShouldResolveThroughCentralDispatcher(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CentralDispatcher dispatcher;
                public void pay(EventWrapper wrapper) {
                    dispatcher.fireOrderEvent(wrapper);
                }
            }
            class CentralDispatcher {
                public void fireOrderEvent(EventWrapper wrapper) {
                    StateMachine sm = new StateMachine();
                    sm.sendEvent(wrapper.getEvent().getType());
                }
            }
            class EventWrapper {
                private RichEvent event = new RichEvent();
                public RichEvent getEvent() { return event; }
            }
            class RichEvent {
                private OrderEvent type = OrderEvent.PAY;
                public OrderEvent getType() { return type; }
            }
            enum OrderEvent { PAY, SHIP }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain chain = resolveChain(context, CONTROLLER, "pay", STATE_MACHINE);
        assertPolyEvents(chain, "OrderEvent.PAY");
        assertLinkedEvent(
                linkEndpointToTransition(chain, MACHINE_CONFIG,
                        transition("NEW", "PAID", "OrderEvent.PAY"),
                        transition("PAID", "SHIPPED", "OrderEvent.SHIP")),
                "OrderEvent.PAY");
    }
}
