package click.kamil.springstatemachineexporter.analysis.service;

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

import java.nio.file.Path;

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

/**
 * Heuristic vs JDT parity for inheritance dispatcher patterns ({@code super}, protected, deep chains).
 */
class JdtInheritanceDispatcherParityTest {

    private static final String API = "com.example.ApiEndpoint";
    private static final String CONTROLLER = "com.example.ChildController";
    private static final String STATE_MACHINE = "com.example.StateMachine";

    @Test
    void jdtShouldMatchHeuristicForSuperOverrideSwitchDelegation(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ApiEndpoint {
                CustomHandler handler;
                public void triggerCheckout() { handler.handleEvent(ActionType.BEGIN_CHECKOUT); }
            }
            abstract class BaseHandler {
                StateMachine machine;
                public void handleEvent(ActionType action) {
                    TransitionEnum transition = switch (action) {
                        case BEGIN_CHECKOUT -> TransitionEnum.CHECKOUT_STARTED;
                        case ABORT_CHECKOUT -> TransitionEnum.CHECKOUT_CANCELLED;
                    };
                    machine.fire(transition);
                }
            }
            class CustomHandler extends BaseHandler {
                @Override
                public void handleEvent(ActionType action) {
                    if (action == null) return;
                    super.handleEvent(action);
                }
            }
            class StateMachine { public void fire(TransitionEnum event) {} }
            enum ActionType { BEGIN_CHECKOUT, ABORT_CHECKOUT }
            enum TransitionEnum { CHECKOUT_STARTED, CHECKOUT_CANCELLED }
            """;
        CodebaseContext context = scanSource(source, tempDir);

        assertParity(context, API, "triggerCheckout", STATE_MACHINE, "fire");

        CallChain chain = resolveChain(context, API, "triggerCheckout", STATE_MACHINE, "fire", EngineKind.HEURISTIC);
        assertMethodChainContains(chain,
                "com.example.ApiEndpoint.triggerCheckout",
                "com.example.CustomHandler.handleEvent",
                "com.example.BaseHandler.handleEvent");
        assertPolyEvents(chain, "TransitionEnum.CHECKOUT_STARTED");
    }

    @Test
    void jdtShouldMatchHeuristicForImplicitInheritedProtectedMethod(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ChildController extends ParentController {
                StateMachine machine;
                public void process() { machine.fire(getInheritedEvent()); }
            }
            abstract class ParentController {
                protected TransitionEnum getInheritedEvent() { return TransitionEnum.STATE_P; }
            }
            class StateMachine { public void fire(TransitionEnum event) {} }
            enum TransitionEnum { STATE_P, STATE_Q }
            """;
        CodebaseContext context = scanSource(source, tempDir);

        assertParity(context, CONTROLLER, "process", STATE_MACHINE, "fire");
        assertPolyEvents(
                resolveChain(context, CONTROLLER, "process", STATE_MACHINE, "fire", EngineKind.JDT),
                "TransitionEnum.STATE_P");
    }

    @Test
    void jdtShouldMatchHeuristicForThisCallToInheritedProtectedMethod(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ChildController extends ParentController {
                StateMachine machine;
                public void process() { machine.fire(this.getInheritedEvent()); }
            }
            abstract class ParentController {
                protected TransitionEnum getInheritedEvent() { return TransitionEnum.STATE_P; }
            }
            class StateMachine { public void fire(TransitionEnum event) {} }
            enum TransitionEnum { STATE_P, STATE_Q }
            """;
        CodebaseContext context = scanSource(source, tempDir);

        assertParity(context, CONTROLLER, "process", STATE_MACHINE, "fire");
        assertPolyEvents(
                resolveChain(context, CONTROLLER, "process", STATE_MACHINE, "fire", EngineKind.JDT),
                "TransitionEnum.STATE_P");
    }

    @Test
    void jdtShouldMatchHeuristicForSuperProtectedSendHelper(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ChildController extends BaseController {
                public void trigger() { super.send("MY_EVENT"); }
            }
            abstract class BaseController {
                StateMachine machine;
                protected void send(String event) { machine.sendEvent(event); }
            }
            class StateMachine { public void sendEvent(String event) {} }
            """;
        CodebaseContext context = scanSource(source, tempDir);

        assertParity(context, "com.example.ChildController", "trigger", "com.example.StateMachine", "sendEvent");
        CallChain chain = resolveChain(
                context, "com.example.ChildController", "trigger", "com.example.StateMachine", "sendEvent", EngineKind.JDT);
        assertMethodChainContains(chain,
                "com.example.ChildController.trigger",
                "com.example.BaseController.send");
        assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("MY_EVENT");
    }

    @Test
    void jdtShouldMatchHeuristicForSuperGetterArgument(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class ChildController extends ParentController {
                StateMachine machine;
                @Override public TransitionEnum getEvent() { return TransitionEnum.STATE_X; }
                public void process() { machine.fire(super.getEvent()); }
            }
            class ParentController {
                public TransitionEnum getEvent() { return TransitionEnum.STATE_Y; }
            }
            class StateMachine { public void fire(TransitionEnum event) {} }
            enum TransitionEnum { STATE_X, STATE_Y }
            """;
        CodebaseContext context = scanSource(source, tempDir);

        assertParity(context, CONTROLLER, "process", STATE_MACHINE, "fire");
        assertPolyEvents(
                resolveChain(context, CONTROLLER, "process", STATE_MACHINE, "fire", EngineKind.JDT),
                "TransitionEnum.STATE_Y");
    }

    @Test
    void jdtShouldMatchHeuristicForControllerServiceCentralDispatcher(@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 }
            """;
        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.ApiController.pay",
                "com.example.OrderService.handlePay",
                "com.example.CentralDispatcher.route");
    }
}
