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.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

class EntryPointBindingExpanderTest {

    @Test
    void shouldExpandPathVariableFromStringSwitchMapper(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class GenericCommandController {
                CommandGateway gateway;
                public void execute(String commandKey) {
                    gateway.executeViaMapper(commandKey);
                }
            }
            class CommandGateway {
                StringCommandMapper mapper;
                void executeViaMapper(String commandKey) {
                    mapper.fromString(commandKey);
                }
            }
            class StringCommandMapper {
                DomainCommand fromString(String commandKey) {
                    return switch (commandKey) {
                        case "order.pay" -> DomainCommand.ORDER_PAY;
                        case "order.ship" -> DomainCommand.ORDER_SHIP;
                        default -> throw new IllegalArgumentException(commandKey);
                    };
                }
            }
            enum DomainCommand { ORDER_PAY, ORDER_SHIP }
            """;
        Files.writeString(tempDir.resolve("App.java"), source);

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

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        Map<String, List<click.kamil.springstatemachineexporter.analysis.model.CallEdge>> graph = engine.buildCallGraph();

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.GenericCommandController")
                .methodName("execute")
                .name("POST /api/commands/{commandKey}")
                .parameters(List.of(EntryPoint.Parameter.builder()
                        .name("commandKey")
                        .type("String")
                        .annotations(List.of("PathVariable"))
                        .build()))
                .build();

        List<Map<String, String>> variants =
                EntryPointBindingExpander.expandPathVariableBindings(entryPoint, context, graph);

        assertThat(variants).extracting(map -> map.get("commandKey"))
                .containsExactlyInAnyOrder("order.pay", "order.ship");
    }

    @Test
    void shouldProduceSinglePayChainForGenericCommandEndpoint(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class GenericCommandController {
                private final CommandGateway gateway = new CommandGateway();
                public void execute(String commandKey) {
                    gateway.executeViaMapper(commandKey);
                }
            }
            enum DomainCommand { ORDER_PAY, ORDER_SHIP }
            enum OrderEvent { PAY, SHIP }
            class CommandGateway {
                private final StringCommandMapper mapper = new StringCommandMapper();
                private final CentralDispatcher central = new CentralDispatcher();
                void executeViaMapper(String commandKey) {
                    DomainCommand command = mapper.fromString(commandKey);
                    central.dispatch(command);
                }
            }
            class StringCommandMapper {
                DomainCommand fromString(String commandKey) {
                    return switch (commandKey) {
                        case "order.pay" -> DomainCommand.ORDER_PAY;
                        case "order.ship" -> DomainCommand.ORDER_SHIP;
                        default -> throw new IllegalArgumentException(commandKey);
                    };
                }
            }
            class CentralDispatcher {
                void dispatch(DomainCommand command) {
                    switch (command) {
                        case ORDER_PAY -> orderPay();
                        case ORDER_SHIP -> orderShip();
                    }
                }
                void orderPay() { fire(OrderEvent.PAY); }
                void orderShip() { fire(OrderEvent.SHIP); }
                void fire(OrderEvent event) {}
            }
            """;
        Files.writeString(tempDir.resolve("App.java"), source);

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

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.GenericCommandController")
                .methodName("execute")
                .name("POST /api/commands/{commandKey}")
                .parameters(List.of(EntryPoint.Parameter.builder()
                        .name("commandKey")
                        .type("String")
                        .annotations(List.of("PathVariable"))
                        .build()))
                .build();
        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.CentralDispatcher")
                .methodName("fire")
                .event("event")
                .build();

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

        assertThat(chains).hasSize(2);
        assertThat(chains.stream().map(chain -> chain.getEntryPoint().getName()))
                .containsExactlyInAnyOrder("POST /api/commands/order.pay", "POST /api/commands/order.ship");
        assertThat(chains.stream().filter(chain -> chain.getEntryPoint().getName().endsWith("order.pay"))).hasSize(1);
        assertThat(chains.stream().filter(chain -> chain.getEntryPoint().getName().endsWith("order.pay")).findFirst().orElseThrow()
                .getMethodChain()).contains("com.example.CentralDispatcher.orderPay");
    }

    @Test
    void shouldExpandPathVariableFromIfElseEqualsChain(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class MachineController {
                StateMachineDispatcher dispatcher;
                public void transition(String machineType, String event) {
                    dispatcher.dispatch(machineType, event);
                }
            }
            class StateMachineDispatcher {
                void dispatch(String machineType, String eventString) {
                    if ("ORDER".equalsIgnoreCase(machineType)) {
                        fireOrder(eventString);
                    } else if ("DOCUMENT".equalsIgnoreCase(machineType)) {
                        fireDocument(eventString);
                    }
                }
                void fireOrder(String eventString) {}
                void fireDocument(String eventString) {}
            }
            """;
        Files.writeString(tempDir.resolve("App.java"), source);

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

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        Map<String, List<click.kamil.springstatemachineexporter.analysis.model.CallEdge>> graph = engine.buildCallGraph();

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.MachineController")
                .methodName("transition")
                .name("POST /api/machine/{machineType}/transition/{event}")
                .parameters(List.of(
                        EntryPoint.Parameter.builder()
                                .name("machineType")
                                .type("String")
                                .annotations(List.of("PathVariable"))
                                .build(),
                        EntryPoint.Parameter.builder()
                                .name("event")
                                .type("String")
                                .annotations(List.of("PathVariable"))
                                .build()))
                .build();

        List<Map<String, String>> variants =
                EntryPointBindingExpander.expandPathVariableBindings(entryPoint, context, graph);

        assertThat(variants).extracting(map -> map.get("machineType"))
                .containsExactlyInAnyOrder("ORDER", "DOCUMENT");
    }
}
