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 HeuristicCallGraphEnginePolymorphicTest {

    @Test
    void shouldResolvePolymorphicInheritanceAcrossChain() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(RichOrderEvent domainEvent) {
                        doProcessOrderEvent(domainEvent);
                    }
                    
                    private void doProcessOrderEvent(RichOrderEvent domainEvent) {
                        OrderEvents eventType = assertSupportedOrderEvent(domainEvent.getType());
                        service.updateOrderState(eventType);
                    }
                    
                    private OrderEvents assertSupportedOrderEvent(OrderEvents e) {
                        return e;
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {
                        // sendEvent
                    }
                }
                
                interface RichOrderEvent {
                    OrderEvents getType();
                }
                
                class CancelOrderEvent implements RichOrderEvent {
                    public OrderEvents getType() { return OrderEvents.CANCELLED; }
                }
                
                class ReceiveOrderEvent implements RichOrderEvent {
                    public OrderEvents getType() { return OrderEvents.RECEIVED; }
                }
                
                enum OrderEvents { CREATE, PAY, CANCELLED, RECEIVED }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_poly");
        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.CANCELLED", "OrderEvents.RECEIVED");
    }

    @Test
    void shouldResolveTernaryAndStringPolymorphicReturns() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent() {
                        service.updateOrderState(new MysteryPayload().getType());
                    }
                }
                
                class OrderService {
                    public void updateOrderState(Object event) {
                        // sendEvent
                    }
                }
                
                class MysteryPayload {
                    public Object getType() {
                        int a = 5;
                        if (a > 10) {
                            return "RAW_STRING_EVENT";
                        }
                        return a > 5 ? OrderEvents.ABCD : OrderEvents.PAY;
                    }
                }
                
                enum OrderEvents { ABCD, PAY }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_ternary_string");
        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.ABCD", "OrderEvents.PAY", "RAW_STRING_EVENT");
    }

    @Test
    void shouldResolvePolymorphicInheritanceThroughDelegation() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(BaseEvent domainEvent) {
                        service.updateOrderState(domainEvent.getType());
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                interface BaseEvent {
                    OrderEvents getType();
                }
                
                abstract class AbstractEvent implements BaseEvent {
                    protected OrderEvents internalGetType() {
                        return OrderEvents.PAY;
                    }
                }
                
                class ConcreteEvent extends AbstractEvent {
                    public OrderEvents getType() { 
                        return internalGetType(); 
                    }
                }
                
                class AnotherConcreteEvent implements BaseEvent {
                    public OrderEvents getType() {
                        return delegateToHelper();
                    }
                    private OrderEvents delegateToHelper() {
                        return OrderEvents.CANCELLED;
                    }
                }
                
                enum OrderEvents { PAY, CANCELLED }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_poly_delegation");
        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 shouldResolveMethodInheritedFromAbstractBaseClass() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(BaseEvent domainEvent) {
                        service.updateOrderState(domainEvent.getType());
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                interface BaseEvent {
                    OrderEvents getType();
                }
                
                abstract class AbstractEvent implements BaseEvent {
                    public OrderEvents getType() {
                        return OrderEvents.PAY;
                    }
                }
                
                // Concrete subclass inherits getType() but doesn't override it!
                class InheritingEvent extends AbstractEvent {
                }
                
                enum OrderEvents { PAY }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_inherited");
        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 shouldResolveConstantsFromAbstractSuperclass() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void handleStatus() {
                        service.process(new PostAcceptanceOrderModificationsRejectedEvent().getType());
                    }
                }
                
                abstract class AbstractPostAcceptanceOrderModificationsEvent {
                    private String event;
                    public AbstractPostAcceptanceOrderModificationsEvent(String event) {
                        this.event = event;
                    }
                    public String getType() { return event; }
                }
                
                class PostAcceptanceOrderModificationsRejectedEvent extends AbstractPostAcceptanceOrderModificationsEvent {
                    public PostAcceptanceOrderModificationsRejectedEvent() {
                        super("POST_ACCEPTANCE_REJECTED");
                    }
                }
                
                class OrderService {
                    public void process(String event) {}
                }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_post_acc");
        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()).contains("POST_ACCEPTANCE_REJECTED");
    }

    @Test
    void shouldResolvePolymorphicEventsThroughAssertSupportedMethod() throws IOException {
        String source = """
                package com.example;
                public class Dispatcher {
                    private HandlerService service;
                    public void dispatchRequest(Payload domainEvent) {
                        doDispatch(domainEvent);
                    }
                    
                    private void doDispatch(Payload domainEvent) {
                        service.handleEvent(checkAndMapEvent(domainEvent.getType()));
                    }
                    
                    private CustomEvents checkAndMapEvent(CustomEvents e) {
                        return e;
                    }
                }
                
                class HandlerService {
                    public void handleEvent(CustomEvents event) {}
                }
                
                interface Payload {
                    CustomEvents getType();
                }
                
                class AlphaPayload implements Payload {
                    public CustomEvents getType() { return CustomEvents.ALPHA; }
                }
                
                class BetaPayload implements Payload {
                    public CustomEvents getType() { return CustomEvents.BETA; }
                }
                
                enum CustomEvents { ALPHA, BETA }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_poly_assert");
        Files.writeString(tempDir.resolve("DispatcherConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.Dispatcher")
                .methodName("dispatchRequest")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.HandlerService")
                .methodName("handleEvent")
                .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("CustomEvents.ALPHA", "CustomEvents.BETA");
    }

    @Test
    void shouldResolvePolymorphicEventsThroughNestedConverters() throws IOException {
        String source = """
                package com.example;
                public class Dispatcher {
                    private HandlerService service;
                    public void dispatchRequest(Payload domainEvent) {
                        doDispatch(domainEvent);
                    }
                    
                    private void doDispatch(Payload domainEvent) {
                        // NESTED CONVERTERS
                        service.handleEvent(verify(sanitize(domainEvent.type())));
                    }
                    
                    private CustomEvents sanitize(CustomEvents e) { return e; }
                    private CustomEvents verify(CustomEvents e) { return e; }
                }
                
                class HandlerService {
                    public void handleEvent(CustomEvents event) {}
                }
                
                interface Payload {
                    CustomEvents type();
                }
                
                class AlphaPayload implements Payload {
                    public CustomEvents type() { return CustomEvents.ALPHA; }
                }
                
                enum CustomEvents { ALPHA, BETA }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_poly_nested");
        Files.writeString(tempDir.resolve("DispatcherConfig.java"), source);

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

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.Dispatcher")
                .methodName("dispatchRequest")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.HandlerService")
                .methodName("handleEvent")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
        System.out.println("DEBUG context.getImplementations(Payload) = " + context.getImplementations("Payload"));
        System.out.println("DEBUG context.getImplementations(com.example.Payload) = " + context.getImplementations("com.example.Payload"));
        System.out.println("DEBUG getPolymorphicEvents() = " + chains.get(0).getTriggerPoint().getPolymorphicEvents());
        assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
            .containsExactly("CustomEvents.ALPHA");
    }

    @Test
    void shouldResolvePolymorphicEventsWithCustomGetterSuffixes() throws IOException {
        String source = """
                package com.example;
                public class Dispatcher {
                    private HandlerService service;
                    public void dispatchRequest(Payload domainEvent) {
                        doDispatch(domainEvent);
                    }
                    
                    private void doDispatch(Payload domainEvent) {
                        // Using .event() instead of .getEvent()
                        service.handleEvent(check(domainEvent.event()));
                    }
                    
                    private CustomEvents check(CustomEvents e) { return e; }
                }
                
                class HandlerService {
                    public void handleEvent(CustomEvents event) {}
                }
                
                interface Payload {
                    CustomEvents event();
                }
                
                class BetaPayload implements Payload {
                    public CustomEvents event() { return CustomEvents.BETA; }
                }
                
                enum CustomEvents { ALPHA, BETA }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_poly_suffix");
        Files.writeString(tempDir.resolve("DispatcherConfig.java"), source);

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

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.Dispatcher")
                .methodName("dispatchRequest")
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.HandlerService")
                .methodName("handleEvent")
                .event("event")
                .build();

        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
        assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
            .containsExactly("CustomEvents.BETA");
    }

    @Test
    void shouldResolveMappedTransitionEnumThroughAbstractBaseClassAndMultipleArgs() throws IOException {
        String source = """
                package com.example;
 
                public class FlowController {
                    private CommandFactory factory;
                    private StateMachine machine;
 
                    public void handleRequest(String payload) {
                        // Creates subclass with multiple args, including intermediate enum
                        BaseCommand cmd = factory.buildCommand(payload);
                        
                        // Fires using the mapped enum from the inherited base method
                        machine.fire(cmd.getTransitionEvent());
                    }
                }
 
                class CommandFactory {
                    public BaseCommand buildCommand(String payload) {
                        // Passing multiple args: String, Enum1, String, int
                        return new WebCommand(payload, SourceType.WEB_API, "meta_v1", 42);
                    }
                }
 
                abstract class BaseCommand {
                    protected String id;
                    protected SourceType sourceType; // The intermediate enum
                    protected String metadata;
 
                    public BaseCommand(String id, SourceType sourceType, String metadata) {
                        this.id = id;
                        this.sourceType = sourceType;
                        this.metadata = metadata;
                    }
 
                    // The tricky part: Inherited method doing the mapping
                    public TransitionEnum getTransitionEvent() {
                        return switch (this.sourceType) {
                            case WEB_API    -> TransitionEnum.ON_WEB_START;
                            case MOBILE_APP -> TransitionEnum.ON_MOBILE_START;
                            case CRON_JOB   -> TransitionEnum.ON_SYSTEM_START;
                        };
                    }
                }
 
                class WebCommand extends BaseCommand {
                    private int priorityLevel;
 
                    public WebCommand(String id, SourceType sourceType, String metadata, int priorityLevel) {
                        super(id, sourceType, metadata); // Engine must track through super()
                        this.priorityLevel = priorityLevel;
                    }
                }
 
                class StateMachine {
                    public void fire(TransitionEnum event) {}
                }
 
                enum SourceType { WEB_API, MOBILE_APP, CRON_JOB }
                enum TransitionEnum { ON_WEB_START, ON_MOBILE_START, ON_SYSTEM_START }
                """;
        java.nio.file.Path tempDir = java.nio.file.Files.createTempDirectory("callgraph_test_complex_inheritance");
        java.nio.file.Files.writeString(tempDir.resolve("OrderConfig.java"), source);
 
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
 
        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
 
        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.FlowController")
                .methodName("handleRequest")
                .build();
 
        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.StateMachine")
                .methodName("fire")
                .event("event")
                .build();
 
        List<CallChain> chains = engine.findChains(List.of(entryPoint), List.of(trigger));
 
        assertThat(chains).hasSize(1);
 
        System.out.println("COMPLEX INHERITANCE polymorphicEvents: " +
                chains.get(0).getTriggerPoint().getPolymorphicEvents());
 
        // The assertion to prove the bug is fixed.
        // It MUST NOT contain "SourceType.WEB_API"
        assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrder("TransitionEnum.ON_WEB_START");
    }
}
