package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import click.kamil.springstatemachineexporter.model.Transition;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Path;
import java.util.List;

import static click.kamil.springstatemachineexporter.analysis.service.CentralDispatcherTestSupport.*;
import static org.assertj.core.api.Assertions.assertThat;

/**
 * Ensures linker-stage polymorphic narrowing caps over-broad call-graph enum lists to configured transitions.
 */
class DispatcherPolyCeilingPipelineTest {

    private static final String MACHINE_CONFIG = "com.example.OrderStateMachineConfig";
    private static final String EVENT_TYPE = "com.example.OrderEvent";
    private static final String STATE_TYPE = "com.example.OrderState";

    @Test
    void linkerShouldCapInjectedFullEnumPolymorphicListToConfiguredTransitions(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CentralDispatcher dispatcher;
                public void pay() { dispatcher.route("PAY"); }
            }
            class CentralDispatcher {
                StateMachine machine;
                public void route(String action) { machine.sendEvent(OrderEvent.valueOf(action)); }
            }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            enum OrderEvent { PAY, SHIP, LOG, META }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain raw = resolveChain(
                context,
                "com.example.ApiController",
                "pay",
                "com.example.StateMachine",
                "sendEvent",
                EngineKind.JDT);

        List<String> bloatedEnum = List.of(
                EVENT_TYPE + ".PAY",
                EVENT_TYPE + ".SHIP",
                EVENT_TYPE + ".LOG",
                EVENT_TYPE + ".META");

        TriggerPoint bloatedTrigger = raw.getTriggerPoint().toBuilder()
                .polymorphicEvents(bloatedEnum)
                .external(true)
                .build();
        CallChain bloatedChain = raw.toBuilder().triggerPoint(bloatedTrigger).build();

        Transition pay = transition("NEW", "PAID", EVENT_TYPE + ".PAY");
        Transition ship = transition("PAID", "SHIPPED", EVENT_TYPE + ".SHIP");

        CallChain linked = linkChain(
                context, bloatedChain, MACHINE_CONFIG, EVENT_TYPE, STATE_TYPE, pay, ship);

        assertThat(linked.getTriggerPoint().getPolymorphicEvents())
                .containsExactly(EVENT_TYPE + ".PAY");
        assertMatchedWithinTransitions(linked, 1);
        assertThat(linked.getMatchedTransitions())
                .extracting(MatchedTransition::getEvent)
                .containsExactly(EVENT_TYPE + ".PAY");
    }

    @Test
    void linkerShouldNotInferAllTransitionEventsForUnresolvedValueOf(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                CentralDispatcher dispatcher;
                public void handle(String action) { dispatcher.route(action); }
            }
            class CentralDispatcher {
                StateMachine machine;
                public void route(String action) { machine.sendEvent(OrderEvent.valueOf(action)); }
            }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            enum OrderEvent { PAY, SHIP, LOG, META }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        CallChain raw = resolveChain(
                context,
                "com.example.ApiController",
                "handle",
                "com.example.StateMachine",
                "sendEvent",
                EngineKind.JDT);

        TriggerPoint stripped = raw.getTriggerPoint().toBuilder()
                .polymorphicEvents(null)
                .build();
        CallChain chain = raw.toBuilder().triggerPoint(stripped).build();

        Transition pay = transition("NEW", "PAID", EVENT_TYPE + ".PAY");
        Transition ship = transition("PAID", "SHIPPED", EVENT_TYPE + ".SHIP");

        CallChain linked = linkChain(
                context, chain, MACHINE_CONFIG, EVENT_TYPE, STATE_TYPE, pay, ship);

        assertThat(linked.getTriggerPoint().getPolymorphicEvents()).isNullOrEmpty();
        assertThat(linked.getMatchedTransitions()).isNullOrEmpty();
    }

    @Test
    void linkerShouldCapCallGraphPolyForDeepDispatcherToTransitionCeiling(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                OrderService orderService;
                public void pay() { orderService.handlePay(); }
            }
            class OrderService {
                CentralDispatcher dispatcher;
                protected void handlePay() { dispatcher.route("PAY"); }
            }
            class CentralDispatcher {
                StateMachine machine;
                public void route(String action) { machine.sendEvent(OrderEvent.valueOf(action)); }
            }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            enum OrderEvent { PAY, SHIP, LOG, META }
            class OrderStateMachineConfig {}
            """;
        CodebaseContext context = scanSource(source, tempDir);

        Transition pay = transition("NEW", "PAID", EVENT_TYPE + ".PAY");
        Transition ship = transition("PAID", "SHIPPED", EVENT_TYPE + ".SHIP");

        CallChain linked = resolveLinkAndAssertSingleMatch(
                context,
                "com.example.ApiController",
                "pay",
                "com.example.StateMachine",
                "sendEvent",
                MACHINE_CONFIG,
                EVENT_TYPE,
                STATE_TYPE,
                EVENT_TYPE + ".PAY",
                EngineKind.JDT,
                pay,
                ship);

        assertThat(linked.getTriggerPoint().getPolymorphicEvents().size()).isLessThan(4);
        assertPolyCappedToTransitionEvents(linked, EVENT_TYPE, pay, ship);
    }
}
