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 static org.assertj.core.api.Assertions.assertThat;

class ConstructorInvocationTest {

    @Test
    void shouldNotBlindlyAddAllConstructorArguments(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(RichOrderEvent domainEvent) {
                        service.updateOrderState(domainEvent.getType());
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                class RichOrderEvent {
                    private OrderEvents type;
                    private String irrelevantInfo;
                    
                    public RichOrderEvent() {
                        this(OrderEvents.PAY, "DEFAULT_INFO");
                    }
                    
                    public RichOrderEvent(OrderEvents type, String info) {
                        this.type = type;
                        this.irrelevantInfo = info;
                    }
                    
                    public OrderEvents getType() { return type; }
                }
                
                enum OrderEvents { PAY }
                """;
        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())
            .contains("OrderEvents.PAY")
            .doesNotContain("DEFAULT_INFO");
    }


    @Test
    void shouldHandleOverloadedConstructorsGracefully(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(RichOrderEvent domainEvent) {
                        service.updateOrderState(domainEvent.getType());
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                class RichOrderEvent {
                    private OrderEvents type;
                    private String irrelevantInfo;
                    private int count;
                    
                    public RichOrderEvent() {
                        this(OrderEvents.PAY, "DEFAULT_INFO");
                    }
                    
                    // Overloaded constructor 1
                    public RichOrderEvent(OrderEvents type, String info) {
                        this.type = type;
                        this.irrelevantInfo = info;
                    }
                    
                    // Overloaded constructor 2 (same number of arguments!)
                    public RichOrderEvent(String info, int count) {
                        this.irrelevantInfo = info;
                        this.count = count;
                        // type is not set here
                    }
                    
                    public OrderEvents getType() { return type; }
                }
                
                enum OrderEvents { PAY }
                """;
        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())
            .contains("OrderEvents.PAY")
            .doesNotContain("DEFAULT_INFO")
            .doesNotContain("count");
    }


    @Test
    void shouldHandleChainedConstructorDelegationGracefully(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(RichOrderEvent domainEvent) {
                        service.updateOrderState(domainEvent.getType());
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                class BaseEvent {
                    protected OrderEvents type;
                    public BaseEvent(OrderEvents type) {
                        this.type = type;
                    }
                }
                
                class RichOrderEvent extends BaseEvent {
                    public RichOrderEvent() {
                        this(OrderEvents.PAY);
                    }
                    
                    public RichOrderEvent(OrderEvents type) {
                        this(type, "DEFAULT_INFO");
                    }
                    
                    public RichOrderEvent(OrderEvents type, String info) {
                        super(type);
                    }
                    
                    public OrderEvents getType() { return type; }
                }
                
                enum OrderEvents { PAY }
                """;
        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())
            .contains("OrderEvents.PAY")
            .doesNotContain("DEFAULT_INFO");
    }

    @Test
    void shouldHandleFieldAccessGracefully(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(RichOrderEvent domainEvent) {
                        service.updateOrderState(domainEvent.getType());
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                class RichOrderEvent {
                    private OrderEvents type;
                    
                    public RichOrderEvent() {
                        this(OrderEvents.PAY);
                    }
                    
                    public RichOrderEvent(OrderEvents type) {
                        this.type = type;
                    }
                    
                    public OrderEvents getType() { 
                        return this.type; // Specifically testing FieldAccess
                    }
                }
                
                enum OrderEvents { PAY }
                """;
        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())
            .contains("OrderEvents.PAY");
    }
    @Test
    void shouldTraceThroughWrappedConstructorArguments(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                import java.util.Objects;
                
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(RichOrderEvent domainEvent) {
                        service.updateOrderState(domainEvent.getType());
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                class RichOrderEvent {
                    private OrderEvents type;
                    
                    public RichOrderEvent() {
                        this(Objects.requireNonNull(OrderEvents.PAY));
                    }
                    
                    public RichOrderEvent(OrderEvents type) {
                        this((OrderEvents) type, "DEFAULT_INFO");
                    }
                    
                    public RichOrderEvent(OrderEvents type, String info) {
                        this.type = Objects.requireNonNull(type);
                    }
                    
                    public OrderEvents getType() { return type; }
                }
                
                enum OrderEvents { PAY }
                """;
        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())
            .contains("OrderEvents.PAY");
    }

    @Test
    void shouldTraceThroughComplexOverloadedConstructors(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(EnterpriseEvent domainEvent) {
                        service.updateOrderState(domainEvent.getType());
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                class EnterpriseEvent {
                    private OrderEvents type;
                    private String info;
                    
                    public EnterpriseEvent() {
                        this(OrderEvents.PAY);
                    }
                    
                    // Matches by parameter count but wrong type!
                    public EnterpriseEvent(String info) {
                        this.info = info;
                    }
                    
                    public EnterpriseEvent(OrderEvents type) {
                        this("info", type);
                    }
                    
                    // Target of previous constructor
                    public EnterpriseEvent(String info, OrderEvents type) {
                        this(type, info, null);
                    }
                    
                    // Final destination
                    public EnterpriseEvent(OrderEvents type, String info, Object extra) {
                        this.type = type;
                        this.info = info;
                    }
                    
                    public OrderEvents getType() { return type; }
                }
                
                enum OrderEvents { PAY }
                """;
        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())
            .contains("OrderEvents.PAY");
    }
}

