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

    @Test
    void shouldUnwrapDeepMethodWrappers() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(RichOrderEvent domainEvent) {
                        OrderEvents eventType = wrap3(wrap2(wrap1(domainEvent.getType())));
                        service.updateOrderState(eventType);
                    }
                    
                    private OrderEvents wrap1(OrderEvents e) { return e; }
                    private OrderEvents wrap2(OrderEvents e) { return e; }
                    private OrderEvents wrap3(OrderEvents e) { return e; }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) { }
                }
                
                class RichOrderEvent {
                    public OrderEvents getType() { return OrderEvents.CREATE; }
                }
                
                enum OrderEvents { CREATE }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_test_deep_wrap");
        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.CREATE");
    }

    @Test
    void shouldProperlyUnwrapDeeplyNestedWrapperCallsForFix1() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(MyEvent event) {
                        OrderEvents eventType = wrap1(wrap2(wrap3(event.getType())));
                        service.updateOrderState(eventType);
                    }
                    
                    private OrderEvents wrap1(OrderEvents e) { return e; }
                    private OrderEvents wrap2(OrderEvents e) { return e; }
                    private OrderEvents wrap3(OrderEvents e) { return e; }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) { }
                }
                
                class MyEvent {
                    public OrderEvents getType() { return OrderEvents.CREATE; }
                }
                enum OrderEvents { CREATE }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_nested_wrap");
        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);
        assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("OrderEvents.CREATE");
    }

    @Test
    void shouldNotExtractClassInstanceCreationEvenWithComplexArgsForFix2() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void createOrder(String oID) {
                        service.processOrderEvent(new OrderCancelledEvent(new OrderId(oID)));
                    }
                }
                class OrderService {
                    public void processOrderEvent(OrderCancelledEvent event) { }
                }
                class OrderCancelledEvent {
                    public OrderCancelledEvent(OrderId id) {}
                }
                class OrderId {
                    public OrderId(String id) {}
                }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_complex_cic");
        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("processOrderEvent").event("event").build();
        List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
        assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getTriggerPoint().getEvent()).isEqualTo("new OrderCancelledEvent(new OrderId(oID))");
    }

    @Test
    void shouldNotCrashOnStaticMethodWrapping() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void handleStatus(DomainEvent domainEvent) {
                        service.process(String.valueOf(domainEvent.getEvent()));
                    }
                }
                
                class DomainEvent {
                    public String getEvent() { return "STATIC_WRAP_EVENT"; }
                }
                
                class OrderService {
                    public void process(String event) {}
                }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_static_wrap");
        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 shouldNotExtractConstantsFromNestedClassInstanceCreations() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void handleNestedConstructors() {
                        // The event contains a nested constructor. The heuristic should NOT extract WRONG_EVENT.
                        OuterEvent e = new OuterEvent("CORRECT_EVENT", new InnerPayload("WRONG_EVENT"));
                        service.process(e.getEventType());
                    }
                }
                
                class OrderService {
                    public void process(String event) {}
                }
                
                class OuterEvent {
                    private String eventType;
                    private InnerPayload payload;
                    public OuterEvent(String eventType, InnerPayload payload) {
                        this.eventType = eventType;
                        this.payload = payload;
                    }
                    public String getEventType() { return eventType; }
                }
                
                class InnerPayload {
                    private String someData;
                    public InnerPayload(String someData) { this.someData = someData; }
                }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_nested_constructor");
        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("handleNestedConstructors").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())
            .containsExactly("CORRECT_EVENT");
    }

    @Test
    void shouldNotExtractConstantsFromNestedArraysOfObjects() throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void handleNestedArrays() {
                        // The event contains a nested array of objects. The heuristic should NOT extract WRONG_EVENT.
                        OuterEvent e = new OuterEvent("CORRECT_EVENT", new InnerPayload[] { new InnerPayload("WRONG_EVENT") });
                        service.process(e.getEventType());
                    }
                }
                
                class OrderService {
                    public void process(String event) {}
                }
                
                class OuterEvent {
                    private String eventType;
                    private InnerPayload[] payloads;
                    public OuterEvent(String eventType, InnerPayload[] payloads) {
                        this.eventType = eventType;
                        this.payloads = payloads;
                    }
                    public String getEventType() { return eventType; }
                }
                
                class InnerPayload {
                    private String someData;
                    public InnerPayload(String someData) { this.someData = someData; }
                }
                """;
        Path tempDir = Files.createTempDirectory("callgraph_nested_array");
        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("handleNestedArrays").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())
            .containsExactly("CORRECT_EVENT");
    }
}
