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.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import org.junit.jupiter.api.Tag;
import static org.assertj.core.api.Assertions.assertThat;

@Tag("heuristic_guess_paths_and_scrape_regex_replace_with_data_flow_analysis")
class HeuristicCallGraphEngineCoreTest {

    @Test
    void shouldFindCallChainWithLambdaMethodReference(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderService {
                    private final EventDispatcher dispatcher = new EventDispatcher();
                    
                    public void processOrder() {
                        dispatcher.dispatch("order", this::sendEvent);
                    }
                    
                    public void sendEvent(String event) {
                        // Dummy trigger
                    }
                }
                
                class EventDispatcher {
                    public void dispatch(String order, java.util.function.Consumer<String> callback) {
                        callback.accept("MY_EVENT");
                    }
                }
                """;
        Files.writeString(tempDir.resolve("OrderService.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderService")
                .methodName("processOrder")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("sendEvent")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        CallChain chain = chains.get(0);
        assertThat(chain.getMethodChain()).containsExactly(
                "com.example.OrderService.processOrder",
                "com.example.OrderService.sendEvent"
        );
    }

    @Test
    void shouldExtractContextMachineId(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class PersisterService {
                    private final StateMachinePersister persister = new StateMachinePersister();
                    
                    public void updateOrderState(String orderId) {
                        StateMachine sm = persister.restore(null, "machine:" + orderId);
                        sm.sendEvent("PAY");
                    }
                }
                
                class StateMachinePersister {
                    public StateMachine restore(Object sm, String contextObj) {
                        return new StateMachine();
                    }
                }
                
                class StateMachine {
                    public void sendEvent(String event) {}
                }
                """;
        Files.writeString(tempDir.resolve("PersisterService.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.PersisterService")
                .methodName("updateOrderState")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.StateMachine")
                .methodName("sendEvent")
                .event("PAY")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        CallChain chain = chains.get(0);
        assertThat(chain.getContextMachineId()).isNotNull();
        // Since it's tracing constant concatenation: "machine:" + orderId, which might resolve dynamically or string format.
        // It should at least be present (it typically extracts 'machine:' + orderId into expression).
    }

    @Test
    void shouldResolveTriggerPointParametersAcrossChain() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void createOrder() {
                        service.updateOrderState(OrderEvents.CREATE);
                    }
                }
                
                class OrderService {
                    private StateMachine sm;
                    public void updateOrderState(OrderEvents event) {
                        sm.sendEvent(event);
                    }
                }
                
                class StateMachine {
                    public void sendEvent(Object event) {}
                }
                
                enum OrderEvents { CREATE, PAY }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("createOrder")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("updateOrderState")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        CallChain chain = chains.get(0);
        assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("OrderEvents.CREATE");
    }

    @Test
    void shouldResolveTriggerPointLocalVariableAcrossChain() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(MyOrderEvent domainEvent) {
                        doProcessOrderEvent(domainEvent);
                    }
                    
                    private void doProcessOrderEvent(MyOrderEvent domainEvent) {
                        OrderEvents eventType = domainEvent.getType();
                        service.updateOrderState(eventType);
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {
                        // sendEvent
                    }
                }
                
                class MyOrderEvent {
                    public OrderEvents getType() { return OrderEvents.PAY; }
                }
                
                enum OrderEvents { CREATE, PAY }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test2");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("processOrderEvent")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("updateOrderState")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        CallChain chain = chains.get(0);
        assertThat(chain.getTriggerPoint().getPolymorphicEvents())
            .as("Resolved event was: " + chain.getTriggerPoint().getEvent())
            .containsExactlyInAnyOrder("OrderEvents.PAY");
    }

    @Test
    void shouldHandleMissingImplementationsGracefully() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(UnknownEvent domainEvent) {
                        service.updateOrderState(domainEvent.getType());
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                // UnknownEvent is not defined in the source (e.g. from a third-party library)
                
                enum OrderEvents { CREATE }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_missing_impl");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("processOrderEvent")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("updateOrderState")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        CallChain chain = chains.get(0);
        assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("domainEvent.getType()");
        assertThat(chain.getTriggerPoint().getPolymorphicEvents()).isNullOrEmpty();
    }

    @Test
    void shouldTraceMultipleLocalVariableAssignments() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(RichOrderEvent domainEvent) {
                        OrderEvents type1 = domainEvent.getType();
                        OrderEvents type2 = type1;
                        OrderEvents type3 = type2;
                        service.updateOrderState(type3);
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                class RichOrderEvent {
                    public OrderEvents getType() { return OrderEvents.RECEIVED; }
                }
                
                enum OrderEvents { RECEIVED }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_multiple_assign");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("processOrderEvent")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("updateOrderState")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        CallChain chain = chains.get(0);
        assertThat(chain.getTriggerPoint().getPolymorphicEvents())
            .containsExactlyInAnyOrder("OrderEvents.RECEIVED");
    }

    @Test
    void shouldStopTracingRecursiveMethodsGracefully() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(RecursiveEvent domainEvent) {
                        service.updateOrderState(domainEvent.getType());
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                class RecursiveEvent {
                    public OrderEvents getType() {
                        return this.getType();
                    }
                }
                
                enum OrderEvents { PAY }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_recursive");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("processOrderEvent")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("updateOrderState")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        CallChain chain = chains.get(0);
        // Should safely complete without stack overflow and return an empty result
        assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("domainEvent.getType()");
        assertThat(chain.getTriggerPoint().getPolymorphicEvents()).isEmpty();
    }

    @Test
    void shouldHandleChainedMethodCallsGracefully() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(EventWrapper wrapper) {
                        service.updateOrderState(wrapper.getEvent().getType());
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                class EventWrapper {
                    public RichEvent getEvent() { return new RichEvent(); }
                }
                
                class RichEvent {
                    public OrderEvents getType() { return OrderEvents.PAY; }
                }
                
                enum OrderEvents { PAY }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_chained");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("processOrderEvent")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("updateOrderState")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        CallChain chain = chains.get(0);
        assertThat(chain.getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrder("OrderEvents.PAY");
    }

    @Test
    void shouldTraceStaticFieldReferences() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private static final OrderEvents CONSTANT_EVENT = OrderEvents.CANCELLED;
                    private OrderService service;
                    public void processOrderEvent() {
                        service.updateOrderState(CONSTANT_EVENT);
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                enum OrderEvents { CANCELLED }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_static_field");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("processOrderEvent")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("updateOrderState")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        CallChain chain = chains.get(0);
        assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("OrderEvents.CANCELLED");
    }

    @Test
    void shouldHandleAnonymousInnerClassesGracefully() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent() {
                        RichEvent event = new RichEvent() {
                            public OrderEvents getType() {
                                return OrderEvents.CREATE;
                            }
                        };
                        service.updateOrderState(event.getType());
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                interface RichEvent {
                    OrderEvents getType();
                }
                
                enum OrderEvents { CREATE }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_anonymous");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("processOrderEvent")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("updateOrderState")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        CallChain chain = chains.get(0);
        // The engine traces the anonymous class's getType() through the RichEvent interface,
        // resolving to OrderEvents.CREATE via the enum return-type fallback.
        assertThat(chain.getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrder("OrderEvents.CREATE");
    }

    @Test
    void shouldLinkCallsInsideLambdasToEnclosingMethod() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent() {
                        Runnable r = () -> service.updateOrderState(OrderEvents.PAY);
                        r.run();
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                enum OrderEvents { PAY }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_lambda_scope");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("processOrderEvent")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("updateOrderState")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        CallChain chain = chains.get(0);
        assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("OrderEvents.PAY");
    }

    @Test
    void shouldHandleTernaryOperatorsGracefully() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(boolean flag) {
                        OrderEvents event = flag ? OrderEvents.PAY : OrderEvents.CANCELLED;
                        service.updateOrderState(event);
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                enum OrderEvents { PAY, CANCELLED }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_ternary");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("processOrderEvent")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("updateOrderState")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        CallChain chain = chains.get(0);
        // It should gracefully fallback to the string representation of the ternary operator
        assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("flag ? OrderEvents.PAY : OrderEvents.CANCELLED");
    }

    @Test
    void shouldTraceAllTextualAssignmentsInTryCatchBlocks() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent() {
                        OrderEvents event;
                        try {
                            event = OrderEvents.PAY;
                        } catch (Exception e) {
                            event = OrderEvents.CANCELLED;
                        }
                        service.updateOrderState(event);
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                enum OrderEvents { PAY, CANCELLED }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_try_catch");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("processOrderEvent")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("updateOrderState")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        CallChain chain = chains.get(0);
        
        assertThat(chain.getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("OrderEvents.PAY", "OrderEvents.CANCELLED");
    }

    @Test
    void shouldResolveMethodReturnConstantFromMultiplePackagesForFix3() throws IOException {
        String source1 = """
                package com.example.api;
                public interface RichEvent {
                    String getType();
                }
                """;
        String source2 = """
                package com.example.impl.a;
                import com.example.api.RichEvent;
                public class EventA implements RichEvent {
                    public String getType() { return "EVENT_A"; }
                }
                """;
        String source3 = """
                package com.example.impl.b;
                import com.example.api.RichEvent;
                public class EventB implements RichEvent {
                    public String getType() { return "EVENT_B"; }
                }
                """;
        String source4 = """
                package com.example.controller;
                import com.example.api.RichEvent;
                public class EventController {
                    private com.example.service.EventService service;
                    public void handleEvent(RichEvent event) {
                        service.process(event.getType());
                    }
                }
                """;
        String source5 = """
                package com.example.service;
                public class EventService {
                    public void process(String type) {}
                }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_multi_pkg");
        Files.writeString(tempDir.resolve("RichEvent.java"), source1);
        Files.writeString(tempDir.resolve("EventA.java"), source2);
        Files.writeString(tempDir.resolve("EventB.java"), source3);
        Files.writeString(tempDir.resolve("EventController.java"), source4);
        Files.writeString(tempDir.resolve("EventService.java"), source5);
        
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder().className("com.example.controller.EventController").methodName("handleEvent").build();
        TriggerPoint trigger = TriggerPoint.builder().className("com.example.service.EventService").methodName("process").event("type").build();
        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
        assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("EVENT_A", "EVENT_B");
    }

    @Test
    void shouldGracefullyHandleComplexMethodChainsWithoutCrashing() throws IOException {
        String source = """
                package com.example;
                import java.util.Optional;
                public class OrderController {
                    private OrderService service;
                    public void handleStatus(DomainEvent domainEvent) {
                        service.process(Optional.of(domainEvent).get().getEvent());
                    }
                }
                
                class DomainEvent {
                    public String getEvent() { return "COMPLEX_EVENT"; }
                }
                
                class OrderService {
                    public void process(String event) {}
                }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_complex_chain");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
        TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
        
        org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
    }

    @Test
    void shouldResolveConstantsFromStringConcatenation() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void handleStatus(DomainEvent domainEvent) {
                        service.process(domainEvent.getCustomEvent());
                    }
                }
                
                class DomainEvent {
                    public static final String PREFIX = "ORDER_";
                    public String getCustomEvent() {
                        return PREFIX + "CREATED";
                    }
                }
                
                class OrderService {
                    public void process(String event) {}
                }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_concat");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
        TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
        
        org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
        org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("ORDER_CREATED");
    }

    @Test
    void shouldResolveConstantsFromStringToString() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void handleStatus(DomainEvent domainEvent) {
                        service.process(domainEvent.getEventString());
                    }
                }
                
                class DomainEvent {
                    public static final String ACTUAL_STRING = "STRING_LITERAL_EVENT";
                    public String getEventString() {
                        return ACTUAL_STRING.toString();
                    }
                }
                
                class OrderService {
                    public void process(String event) {}
                }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_string_tostring");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
        TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
        
        org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
        org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("STRING_LITERAL_EVENT");
    }

    @Test
    void shouldResolveEventFromLambda() throws IOException {
        String source = """
                package com.example;
                import java.util.List;
                public class OrderController {
                    private OrderService service;
                    public void handleList(List<LambdaEvent> events) {
                        events.forEach(e -> service.process(e.getType()));
                    }
                }
                
                class LambdaEvent {
                    private String type;
                    public LambdaEvent(String typeArg) { this.type = typeArg; }
                    public String getType() { return type; }
                }
                
                class OrderService {
                    public void process(String event) {}
                }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_lambda");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleList").build();
        TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
        System.out.println("DEBUG LAMBDA CHAINS: " + (chains.isEmpty() ? "empty" : chains.get(0).getTriggerPoint().getPolymorphicEvents()));
        
        org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
        org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).isNullOrEmpty();
    }

    @Test
    void shouldResolveEventFromAnonymousClass() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void handleStatus() {
                        service.process(new EventProvider() {
                            @Override
                            public String getEvent() { return "ANONYMOUS_EVENT"; }
                        }.getEvent());
                    }
                }
                
                interface EventProvider {
                    String getEvent();
                }
                
                class OrderService {
                    public void process(String event) {}
                }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_anonymous");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
        TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
        
        org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
        org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("ANONYMOUS_EVENT");
    }

    @Test
    void shouldResolveEventFromBranchingAssignments() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void handleStatus(boolean isRefund) {
                        String event;
                        if (isRefund) {
                            event = "REFUND_EVENT";
                        } else {
                            event = "CHARGE_EVENT";
                        }
                        service.process(event);
                    }
                }
                
                class OrderService {
                    public void process(String event) {}
                }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_branch");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
        TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
        
        org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
        List<String> resolvedEvents = chains.stream().flatMap(c -> c.getTriggerPoint().getPolymorphicEvents().stream()).toList();
        org.assertj.core.api.Assertions.assertThat(resolvedEvents).containsExactlyInAnyOrder("REFUND_EVENT", "CHARGE_EVENT");
    }

    @Test
    void shouldResolveEventFromMethodReference() throws IOException {
        String source = """
                package com.example;
                import java.util.List;
                public class OrderController {
                    private OrderService service;
                    public void handleMethodRef() {
                        List<RefEvent> events = List.of(new RefEvent("METHOD_REF_EVENT"));
                        events.forEach(this::delegateProcessing);
                    }
                    private void delegateProcessing(RefEvent e) {
                        service.process(e.getType());
                    }
                }
                
                class OrderService {
                    public void process(String event) {}
                }
                
                class RefEvent {
                    private String type;
                    public RefEvent(String type) { this.type = type; }
                    public String getType() { return type; }
                }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_method_ref");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleMethodRef").build();
        TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
        
        org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
        org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("METHOD_REF_EVENT");
    }

    @Test
    void shouldResolveEventFromJavaRecord() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void handleRecord() {
                        RecordEvent e = new RecordEvent("RECORD_EVENT", 42);
                        service.process(e.type());
                    }
                }
                
                class OrderService {
                    public void process(String event) {}
                }
                
                record RecordEvent(String type, int someId) {}
                """;
        Path tempDir = Files.createTempDirectory("callgraph_record");
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleRecord").build();
        TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
        
        org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
        org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("RECORD_EVENT");
    }

    @Test
    void shouldResolveEventFromReactiveMethodReference(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private StateMachine stateMachine;
                    public void handleReactive() {
                        Mono.just("REACTIVE_EVENT").flatMap(stateMachine::sendEvent);
                    }
                }
                
                class StateMachine {
                    public void sendEvent(String event) {}
                }
                
                class Mono {
                    public static Mono just(Object obj) { return new Mono(); }
                    public Mono flatMap(Object mapper) { return this; }
                }
                """;
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleReactive").build();
        TriggerPoint trigger = TriggerPoint.builder().className("com.example.StateMachine").methodName("sendEvent").event("event").build();
        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
        
        org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
        org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("REACTIVE_EVENT");
    }

    @Test
    void shouldResolveEventFromNestedReactiveFlatMap(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private StateMachine stateMachine;
                    public void handleReactive() {
                        Mono.just("IGNORED")
                                .flatMap(x -> Mono.just("NESTED_REACTIVE_EVENT"))
                                .flatMap(stateMachine::sendEvent);
                    }
                }
                
                class StateMachine {
                    public void sendEvent(String event) {}
                }
                
                class Mono {
                    public static Mono just(Object obj) { return new Mono(); }
                    public Mono flatMap(Object mapper) { return this; }
                }
                """;
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleReactive").build();
        TriggerPoint trigger = TriggerPoint.builder().className("com.example.StateMachine").methodName("sendEvent").event("event").build();
        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
        org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrder("NESTED_REACTIVE_EVENT");
    }

    @Test
    void shouldExtractEventFromSwitchExpressionAndParentheses(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class SwitchOrderService {
                    public void processOrder(int type) {
                        String e = (((switch(type) {
                            case 1 -> "PAY";
                            default -> { yield "CANCEL"; }
                        })));
                        sendEvent(e);
                    }
                    
                    public void sendEvent(String event) {
                    }
                }
                """;
        Files.writeString(tempDir.resolve("SwitchOrderService.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.SwitchOrderService")
                .methodName("processOrder")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.SwitchOrderService")
                .methodName("sendEvent")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        System.out.println("POLY EVENTS: " + chains.get(0).getTriggerPoint().getPolymorphicEvents());
        assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrder("PAY", "CANCEL");
    }
}
