package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.CallChain;
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;

/**
 * Deep inheritance dispatcher stacks: multi-level {@code super} and template-method patterns.
 */
class DeepDispatcherHierarchyTest {

    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 jdtShouldMatchHeuristicForMultiLevelSuperDelegation(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                GrandChildHandler handler;
                public void pay() { handler.entry(OrderEvent.PAY); }
            }
            class GrandChildHandler extends MiddleHandler {
                void entry(OrderEvent event) { super.route(event); }
            }
            class MiddleHandler extends BaseHandler {
                protected void route(OrderEvent event) { super.dispatch(event); }
            }
            class BaseHandler {
                StateMachine machine;
                protected void dispatch(OrderEvent event) { machine.sendEvent(event); }
            }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            enum OrderEvent { PAY, SHIP, LOG, META }
            """;
        CodebaseContext context = scanSource(source, tempDir);

        assertParity(context, "com.example.ApiController", "pay", "com.example.StateMachine", "sendEvent");

        CallChain chain = resolveChain(
                context, "com.example.ApiController", "pay", "com.example.StateMachine", "sendEvent", EngineKind.JDT);
        assertMethodChainContains(chain,
                "com.example.GrandChildHandler.entry",
                "com.example.MiddleHandler.route",
                "com.example.BaseHandler.dispatch");
        assertPolyEvents(chain, "OrderEvent.PAY");
    }

    @Test
    void jdtShouldMatchHeuristicForTemplateMethodProtectedDispatch(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                OrderHandler handler;
                public void pay() { handler.pay(); }
            }
            abstract class AbstractHandler {
                StateMachine machine;
                public void pay() { dispatch(buildPayEvent()); }
                protected abstract OrderEvent buildPayEvent();
                protected void dispatch(OrderEvent event) { machine.sendEvent(event); }
            }
            class OrderHandler extends AbstractHandler {
                @Override protected OrderEvent buildPayEvent() { return OrderEvent.PAY; }
            }
            class StateMachine { public void sendEvent(OrderEvent e) {} }
            enum OrderEvent { PAY, SHIP, LOG, META }
            """;
        CodebaseContext context = scanSource(source, tempDir);

        assertParity(context, "com.example.ApiController", "pay", "com.example.StateMachine", "sendEvent");
        assertPolyEvents(
                resolveChain(context, "com.example.ApiController", "pay", "com.example.StateMachine", "sendEvent", EngineKind.HEURISTIC),
                "OrderEvent.PAY");
    }

    @Test
    void pipelineShouldLinkMultiLevelSuperToSingleConfiguredTransition(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                GrandChildHandler handler;
                public void pay() { handler.entry(OrderEvent.PAY); }
            }
            class GrandChildHandler extends MiddleHandler {
                void entry(OrderEvent event) { super.route(event); }
            }
            class MiddleHandler extends BaseHandler {
                protected void route(OrderEvent event) { super.dispatch(event); }
            }
            class BaseHandler {
                StateMachine machine;
                protected void dispatch(OrderEvent event) { machine.sendEvent(event); }
            }
            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);

        assertPolyCappedToTransitionEvents(linked, EVENT_TYPE, pay, ship);
        assertThat(linked.getTriggerPoint().getPolymorphicEvents())
                .containsExactly(EVENT_TYPE + ".PAY");
    }

    @Test
    void pipelineShouldLinkTemplateMethodDispatchToSingleConfiguredTransition(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiController {
                OrderHandler handler;
                public void pay() { handler.pay(); }
            }
            abstract class AbstractHandler {
                StateMachine machine;
                public void pay() { dispatch(buildPayEvent()); }
                protected abstract OrderEvent buildPayEvent();
                protected void dispatch(OrderEvent event) { machine.sendEvent(event); }
            }
            class OrderHandler extends AbstractHandler {
                @Override protected OrderEvent buildPayEvent() { return OrderEvent.PAY; }
            }
            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.HEURISTIC,
                pay,
                ship);

        assertPolyCappedToTransitionEvents(linked, EVENT_TYPE, pay, ship);
    }
}
