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.LinkResolution;
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.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;

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

/**
 * Adversarial coverage for {@code EventArgumentSelector} wiring in detector / ACE / EI.
 */
class EventArgumentSelectorBugHuntTest {

    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 sendEventHeadersThenEventResolvesPay(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            import java.util.Map;
            public class ApiController {
                Dispatcher dispatcher;
                public void pay() { dispatcher.fire(Map.of(), "PAY"); }
            }
            class Dispatcher {
                StateMachine machine;
                void fire(Map<String, Object> headers, String code) {
                    machine.sendEvent(headers, OrderEvent.valueOf(code));
                }
            }
            class StateMachine { public void sendEvent(Map<String, Object> headers, OrderEvent e) {} }
            enum OrderEvent { PAY, SHIP, CANCEL }
            enum OrderState { NEW, PAID, SHIPPED, CANCELLED }
            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.getLinkResolution()).isEqualTo(LinkResolution.RESOLVED);
        assertThat(linked.getTriggerPoint().getEvent())
                .as("event stamp must not be the headers arg")
                .doesNotContain("headers")
                .doesNotContain("Map");
    }

    @Test
    void sendEventEventThenStateStillDetectsSourceState(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderService {
                    private StateMachine sm;
                    public void pay() {
                        sm.sendEvent(OrderEvent.PAY, OrderState.PENDING);
                    }
                }
                class StateMachine {
                    public void sendEvent(OrderEvent e, OrderState s) {}
                }
                enum OrderState { PENDING }
                enum OrderEvent { PAY }
                """;
        Files.writeString(tempDir.resolve("OrderService.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        GenericEventDetector detector = new GenericEventDetector(
                context, new click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver(),
                Collections.emptyList());
        List<TriggerPoint> triggers = detector.detect(
                (org.eclipse.jdt.core.dom.CompilationUnit)
                        context.getTypeDeclaration("com.example.OrderService").getRoot());
        assertThat(triggers).hasSize(1);
        assertThat(triggers.get(0).getSourceState()).isEqualTo("PENDING");
        assertThat(triggers.get(0).getEvent()).contains("PAY");
    }

    @Test
    void sendEventHeadersThenValueOfPolyUsesEventArg(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                import java.util.Map;
                public class ApiController {
                    StateMachine sm;
                    public void process(Map<String, Object> headers, String action) {
                        sm.sendEvent(headers, OrderEvent.valueOf(action.toUpperCase()));
                    }
                }
                enum OrderEvent { PAY, SHIP, CANCEL }
                class StateMachine {
                    public void sendEvent(Map<String, Object> headers, OrderEvent e) {}
                }
                """;
        Files.writeString(tempDir.resolve("ApiController.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        EntryPoint entry = EntryPoint.builder()
                .className("com.example.ApiController")
                .methodName("process")
                .build();
        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.StateMachine")
                .methodName("sendEvent")
                .event("e")
                .build();
        List<CallChain> chains = engine.findChains(List.of(entry), List.of(trigger));
        assertThat(chains).isNotEmpty();
        assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
                .contains("OrderEvent.PAY", "OrderEvent.SHIP", "OrderEvent.CANCEL");
    }
}
