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

    @Test
    void shouldTraceLocalVariableInGetter(@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 {
                    public OrderEvents getType() { 
                        OrderEvents e = OrderEvents.PAY;
                        return e; 
                    }
                }
                
                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("e"); // Should not blindly add the variable name
    }

    @Test
    void shouldTraceMultipleAssignments(@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 {
                    public OrderEvents getType() { 
                        OrderEvents e = null;
                        if (System.currentTimeMillis() % 2 == 0) {
                            e = OrderEvents.PAY;
                        } else {
                            e = OrderEvents.CANCEL;
                        }
                        return e; 
                    }
                }
                
                enum OrderEvents { PAY, CANCEL }
                """;
        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.CANCEL");
    }

    @Test
    void shouldNotUnwrapLocalDomainMethod(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(RichOrderEvent domainEvent) {
                        OrderEvents resolved = validateEvent(domainEvent);
                        service.updateOrderState(resolved);
                    }
                    
                    private OrderEvents validateEvent(RichOrderEvent event) {
                        return OrderEvents.CANCEL;
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                class RichOrderEvent {
                    public OrderEvents getType() { 
                        return OrderEvents.PAY; 
                    }
                }
                
                enum OrderEvents { PAY, CANCEL }
                """;
        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.CANCEL");
    }
    @Test
    void shouldUnwrapExplicitPassthroughMethod(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    private EventValidator validator;
                    public void processOrderEvent(RichOrderEvent domainEvent) {
                        OrderEvents resolved = validator.passthrough(domainEvent.getType());
                        service.updateOrderState(resolved);
                    }
                }
                
                class EventValidator {
                    public OrderEvents passthrough(OrderEvents e) {
                        if (e == null) throw new IllegalArgumentException();
                        return e;
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                class RichOrderEvent {
                    public OrderEvents getType() { 
                        return OrderEvents.PAY; 
                    }
                }
                
                enum OrderEvents { PAY, CANCEL }
                """;
        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 shouldFallbackToPrefixMatchingForExternalMethods(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderController {
                    private OrderService service;
                    public void processOrderEvent(RichOrderEvent domainEvent) {
                        OrderEvents resolved = UnknownExternalClass.validate(domainEvent.getType());
                        service.updateOrderState(resolved);
                    }
                }
                
                class OrderService {
                    public void updateOrderState(OrderEvents event) {}
                }
                
                class RichOrderEvent {
                    public OrderEvents getType() { 
                        return OrderEvents.PAY; 
                    }
                }
                
                enum OrderEvents { PAY, CANCEL }
                """;
        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())
            .doesNotContain("OrderEvents.PAY");
    }
}
