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.AstUtils;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import click.kamil.springstatemachineexporter.ast.common.JdtDataFlowModel;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MethodInvocation;
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;

/**
 * Regression tests for path-binding cache-once-per-chain and index-first getter-chain hops.
 */
class PathResolutionOptimizationTest {

    @Test
    void findChainsShouldAttachPathBindingsOncePerChain(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                private final CommandGateway gateway = new CommandGateway();
                public void pay() { gateway.executeViaMapper("order.pay"); }
            }
            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 entry = EntryPoint.builder().className("com.example.ApiController").methodName("pay").build();
        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.CentralDispatcher")
                .methodName("fire")
                .event("event")
                .build();

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

        assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getPathBindings())
                .containsEntry("command", "com.example.DomainCommand.ORDER_PAY");
        assertThat(chains.get(0).getMethodChain()).contains("com.example.CentralDispatcher.orderPay");
    }

    @Test
    void indexFirstGetterChainHopShouldResolveFieldDefaultChain(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class OrderService {
                public void pay(Order order) {
                    order.getPayload().getType();
                }
            }
            class Order {
                private Payload payload = new Payload();
                public Payload getPayload() { return payload; }
            }
            class Payload {
                private OrderEvent type = OrderEvent.PAY;
                public OrderEvent getType() { return type; }
            }
            enum OrderEvent { PAY, SHIP }
            """;
        Files.writeString(tempDir.resolve("App.java"), source);

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

        JdtDataFlowModel dataFlowModel = new JdtDataFlowModel(context);
        Expression found = AstUtils.findExpressionInMethod(
                "com.example.OrderService.pay", "order.getPayload().getType()", context);
        assertThat(found).isInstanceOf(MethodInvocation.class);

        String resolved = dataFlowModel.resolveValue((MethodInvocation) found, context);
        assertThat(resolved).contains("PAY");
    }
}
