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

    @Test
    void shouldResolvePolymorphicDispatchCalls(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                
                public class OrderController {
                    private OrderServiceRegistry registry;
                    
                    public void handleRequest(String order, String event) {
                        registry.getOrderService(order).processOrderEvent(event);
                    }
                }
                
                class OrderServiceRegistry {
                    public OrderService getOrderService(String order) {
                        return null; // dynamic lookup
                    }
                }
                
                interface OrderService {
                    void processOrderEvent(String event);
                }
                
                class PlatformOrderService implements OrderService {
                    public void processOrderEvent(String event) {
                        // trigger state machine
                    }
                }
                
                class TransportOrderService implements OrderService {
                    public void processOrderEvent(String event) {
                        // trigger state machine
                    }
                }
                """;
        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("handleRequest")
                .build();

        TriggerPoint triggerPlatform = TriggerPoint.builder()
                .className("com.example.PlatformOrderService")
                .methodName("processOrderEvent")
                .event("event")
                .build();

        TriggerPoint triggerTransport = TriggerPoint.builder()
                .className("com.example.TransportOrderService")
                .methodName("processOrderEvent")
                .event("event")
                .build();

        List<CallChain> chainsPlatform = builder.findChains(List.of(entryPoint), List.of(triggerPlatform));
        List<CallChain> chainsTransport = builder.findChains(List.of(entryPoint), List.of(triggerTransport));

        assertThat(chainsPlatform).hasSize(1);
        assertThat(chainsPlatform.get(0).getMethodChain())
                .containsExactly(
                        "com.example.OrderController.handleRequest",
                        "com.example.PlatformOrderService.processOrderEvent"
                );

        assertThat(chainsTransport).hasSize(1);
        assertThat(chainsTransport.get(0).getMethodChain())
                .containsExactly(
                        "com.example.OrderController.handleRequest",
                        "com.example.TransportOrderService.processOrderEvent"
                );
    }

    @Test
    void shouldFilterPolymorphicDispatchContextually(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                
                public class LogisticsController {
                    private LogisticsService service;
                    
                    public void handle(String event) {
                        service.process(event);
                    }
                }
                
                abstract class AbstractOrderService {
                    public void process(String event) {
                        String resolved = assertSupportedOrderEvent(event);
                        sendEvent(resolved);
                    }
                    
                    protected abstract String assertSupportedOrderEvent(String event);
                    
                    public void sendEvent(String ev) {}
                }
                
                class LogisticsService extends AbstractOrderService {
                    @Override
                    protected String assertSupportedOrderEvent(String event) {
                        return LogisticsEvent.valueOf(event).name();
                    }
                }
                
                class ComputerStoreService extends AbstractOrderService {
                    @Override
                    protected String assertSupportedOrderEvent(String event) {
                        return ComputerEvent.valueOf(event).name();
                    }
                }
                
                enum LogisticsEvent { DELIVERED, RETURNED }
                enum ComputerEvent { SHIPPED, REFUNDED }
                """;
        Files.writeString(tempDir.resolve("OrderConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.LogisticsController")
                .methodName("handle")
                .build();

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

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

        assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrder("LogisticsEvent.DELIVERED", "LogisticsEvent.RETURNED");
    }

    @Test
    void shouldResolveMapBasedDynamicDispatch(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                import java.util.Map;
                import java.util.HashMap;
                
                public class DispatcherController {
                    private Map<String, AbstractService> serviceMap = new HashMap<>();
                    
                    public DispatcherController(LogisticsService logistics, ComputerStoreService computerStore) {
                        serviceMap.put("LOGISTICS", logistics);
                        serviceMap.put("COMPUTER", computerStore);
                    }
                    
                    public void handle(String machineType, String event) {
                        serviceMap.get(machineType).process(event);
                    }
                }
                
                abstract class AbstractService {
                    public void process(String event) {
                        String resolved = assertSupportedEvent(event);
                        sendEvent(resolved);
                    }
                    protected abstract String assertSupportedEvent(String event);
                    public void sendEvent(String ev) {}
                }
                
                class LogisticsService extends AbstractService {
                    @Override
                    protected String assertSupportedEvent(String event) {
                        return LogisticsEvent.valueOf(event).name();
                    }
                }
                
                class ComputerStoreService extends AbstractService {
                    @Override
                    protected String assertSupportedEvent(String event) {
                        return ComputerEvent.valueOf(event).name();
                    }
                }
                
                enum LogisticsEvent { DELIVERED, RETURNED }
                enum ComputerEvent { SHIPPED, REFUNDED }
                """;
        Files.writeString(tempDir.resolve("DispatchConfig.java"), source);

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

        HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.DispatcherController")
                .methodName("handle")
                .build();

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

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

        assertThat(chains).hasSize(2);
        
        CallChain chainLogistics = chains.stream()
                .filter(c -> "machineType == \"LOGISTICS\"".equals(c.getTriggerPoint().getConstraint()))
                .findFirst().orElseThrow();
        assertThat(chainLogistics.getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrder("LogisticsEvent.DELIVERED", "LogisticsEvent.RETURNED");

        CallChain chainComputer = chains.stream()
                .filter(c -> "machineType == \"COMPUTER\"".equals(c.getTriggerPoint().getConstraint()))
                .findFirst().orElseThrow();
        assertThat(chainComputer.getTriggerPoint().getPolymorphicEvents())
                .containsExactlyInAnyOrder("ComputerEvent.SHIPPED", "ComputerEvent.REFUNDED");
    }
}
