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

/**
 * Guards map-routed endpoint resolution and per-machine polymorphic event isolation when the
 * call graph and accessor caches are reused across multiple {@link HeuristicCallGraphEngine#findChains}
 * invocations on the same {@link CodebaseContext}.
 */
class PerformanceCacheMultiMachineIsolationTest {

    private static final String ORDER_CONTROLLER = "com.example.OrderController";
    private static final String SHIPMENT_CONTROLLER = "com.example.ShipmentController";
    private static final String ORDER_MACHINE = "com.example.OrderMachine";
    private static final String SHIPMENT_MACHINE = "com.example.ShipmentMachine";

    @Test
    void mapRoutedEndpointsShouldResolveSingleEventPerLiteralEndpoint(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            import java.util.Map;
            public class OrderController {
                OrderMachine sm;
                public void payOrder() { sm.sendEvent(Routes.ROUTES.get("order.pay")); }
                public void shipOrder() { sm.sendEvent(Routes.ROUTES.get("order.ship")); }
            }
            class Routes {
                static final Map<String, OrderEvent> ROUTES = Map.of(
                    "order.pay", OrderEvent.PAY,
                    "order.ship", OrderEvent.SHIP
                );
            }
            enum OrderEvent { PAY, SHIP, CANCEL }
            class OrderMachine { public void sendEvent(OrderEvent e) {} }
            """;
        Files.writeString(tempDir.resolve("App.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        TriggerPoint trigger = TriggerPoint.builder()
                .className(ORDER_MACHINE)
                .methodName("sendEvent")
                .event("e")
                .build();

        EntryPoint payEntry = EntryPoint.builder().className(ORDER_CONTROLLER).methodName("payOrder").build();
        EntryPoint shipEntry = EntryPoint.builder().className(ORDER_CONTROLLER).methodName("shipOrder").build();

        CallChain payChain = engine.findChains(List.of(payEntry), List.of(trigger)).get(0);
        CallChain shipChain = engine.findChains(List.of(shipEntry), List.of(trigger)).get(0);

        assertThat(payChain.getTriggerPoint().getPolymorphicEvents()).containsExactly("OrderEvent.PAY");
        assertThat(shipChain.getTriggerPoint().getPolymorphicEvents()).containsExactly("OrderEvent.SHIP");

        assertThat(context.getCache()).containsKey("heuristicCallGraph");

        CallChain payAgain = engine.findChains(List.of(payEntry), List.of(trigger)).get(0);
        CallChain shipAgain = engine.findChains(List.of(shipEntry), List.of(trigger)).get(0);

        assertThat(payAgain.getTriggerPoint().getPolymorphicEvents())
                .isEqualTo(payChain.getTriggerPoint().getPolymorphicEvents());
        assertThat(shipAgain.getTriggerPoint().getPolymorphicEvents())
                .isEqualTo(shipChain.getTriggerPoint().getPolymorphicEvents());
    }

    @Test
    void separateMachinesShouldNotCrossContaminateMapRoutedResolution(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            import java.util.Map;
            public class OrderController {
                OrderMachine orderMachine;
                public void pay() { orderMachine.sendEvent(OrderRoutes.ROUTES.get("order.pay")); }
            }
            public class ShipmentController {
                ShipmentMachine shipmentMachine;
                public void dispatch() { shipmentMachine.sendEvent(ShipmentRoutes.ROUTES.get("shipment.dispatch")); }
            }
            class OrderRoutes {
                static final Map<String, OrderEvent> ROUTES = Map.of("order.pay", OrderEvent.PAY);
            }
            class ShipmentRoutes {
                static final Map<String, ShipmentEvent> ROUTES = Map.of("shipment.dispatch", ShipmentEvent.DISPATCH);
            }
            enum OrderEvent { PAY }
            enum ShipmentEvent { DISPATCH }
            class OrderMachine { public void sendEvent(OrderEvent e) {} }
            class ShipmentMachine { public void sendEvent(ShipmentEvent e) {} }
            """;
        Files.writeString(tempDir.resolve("App.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);

        EntryPoint orderEntry = EntryPoint.builder().className(ORDER_CONTROLLER).methodName("pay").build();
        EntryPoint shipmentEntry = EntryPoint.builder().className(SHIPMENT_CONTROLLER).methodName("dispatch").build();
        TriggerPoint orderTrigger = TriggerPoint.builder()
                .className(ORDER_MACHINE)
                .methodName("sendEvent")
                .event("e")
                .build();
        TriggerPoint shipmentTrigger = TriggerPoint.builder()
                .className(SHIPMENT_MACHINE)
                .methodName("sendEvent")
                .event("e")
                .build();

        engine.findChains(List.of(orderEntry), List.of(orderTrigger));
        CallChain shipmentChain = engine.findChains(List.of(shipmentEntry), List.of(shipmentTrigger)).get(0);
        assertThat(shipmentChain.getTriggerPoint().getPolymorphicEvents()).containsExactly("ShipmentEvent.DISPATCH");

        CallChain orderChain = engine.findChains(List.of(orderEntry), List.of(orderTrigger)).get(0);
        assertThat(orderChain.getTriggerPoint().getPolymorphicEvents()).containsExactly("OrderEvent.PAY");
    }
}
