package click.kamil.springstatemachineexporter.analysis.enricher;

import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
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.Event;
import click.kamil.springstatemachineexporter.model.State;
import click.kamil.springstatemachineexporter.model.Transition;
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;

/**
 * Regression for enterprise multi-module scans: a globally ambiguous enum simple name must not
 * block machine-scoped {@code valueOf} widens from linking when polymorphic events use import-style
 * labels and the machine event type is known.
 */
class TrustedEnumWidenLinkingRegressionTest {

    @Test
    void shouldLinkTrustedImportStyleWidenWhenEnumSimpleNameIsGloballyAmbiguous(@TempDir Path tempDir) throws Exception {
        Files.createDirectories(tempDir.resolve("a"));
        Files.createDirectories(tempDir.resolve("b"));
        Files.writeString(tempDir.resolve("a/OrderEvent.java"),
                "package a; public enum OrderEvent { PAY }");
        Files.writeString(tempDir.resolve("b/OrderEvent.java"),
                "package b; public enum OrderEvent { CANCEL }");

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        assertThat(context.isAmbiguousSimpleName("OrderEvent")).isTrue();

        Transition pay = new Transition();
        pay.setSourceStates(List.of(State.of("NEW", "com.example.order.OrderState.NEW")));
        pay.setTargetStates(List.of(State.of("PAID", "com.example.order.OrderState.PAID")));
        pay.setEvent(Event.of("PAY", "com.example.order.OrderEvent.PAY"));

        Transition ship = new Transition();
        ship.setSourceStates(List.of(State.of("PAID", "com.example.order.OrderState.PAID")));
        ship.setTargetStates(List.of(State.of("SHIPPED", "com.example.order.OrderState.SHIPPED")));
        ship.setEvent(Event.of("SHIP", "com.example.order.OrderEvent.SHIP"));

        CallChain chain = CallChain.builder()
                .triggerPoint(TriggerPoint.builder()
                        .event("OrderEvent.valueOf(eventStr)")
                        .ambiguous(true)
                        .external(false)
                        .polymorphicEvents(List.of("OrderEvent.PAY"))
                        .build())
                .build();

        AnalysisResult result = AnalysisResult.builder()
                .name("com.example.config.OrderStateMachineConfiguration")
                .eventTypeFqn("com.example.order.OrderEvent")
                .stateTypeFqn("com.example.order.OrderState")
                .transitions(List.of(pay, ship))
                .metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
                .build();

        new TransitionLinkerEnricher().enrich(result, null, null);

        CallChain linked = result.getMetadata().getCallChains().get(0);
        assertThat(linked.getLinkResolution()).isEqualTo(LinkResolution.RESOLVED);
        assertThat(linked.getMatchedTransitions()).hasSizeGreaterThanOrEqualTo(1);
        assertThat(linked.getTriggerPoint().getPolymorphicEvents())
                .containsExactly("com.example.order.OrderEvent.PAY");
    }

    @Test
    void shouldKeepTrustedWidenCallChainWhenEnumSimpleNameIsGloballyAmbiguous(@TempDir Path tempDir) throws Exception {
        Files.createDirectories(tempDir.resolve("a"));
        Files.createDirectories(tempDir.resolve("b"));
        Files.writeString(tempDir.resolve("a/OrderEvent.java"),
                "package a; public enum OrderEvent { PAY }");
        Files.writeString(tempDir.resolve("b/OrderEvent.java"),
                "package b; public enum OrderEvent { CANCEL }");

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        assertThat(context.isAmbiguousSimpleName("OrderEvent")).isTrue();

        Transition pay = new Transition();
        pay.setSourceStates(List.of(State.of("NEW", "com.example.order.OrderState.NEW")));
        pay.setTargetStates(List.of(State.of("PAID", "com.example.order.OrderState.PAID")));
        pay.setEvent(Event.of("PAY", "com.example.order.OrderEvent.PAY"));

        Transition ship = new Transition();
        ship.setSourceStates(List.of(State.of("PAID", "com.example.order.OrderState.PAID")));
        ship.setTargetStates(List.of(State.of("SHIPPED", "com.example.order.OrderState.SHIPPED")));
        ship.setEvent(Event.of("SHIP", "com.example.order.OrderEvent.SHIP"));

        CallChain chain = CallChain.builder()
                .triggerPoint(TriggerPoint.builder()
                        .event("OrderEvent.valueOf(eventStr)")
                        .ambiguous(true)
                        .external(false)
                        .polymorphicEvents(List.of("OrderEvent.PAY"))
                        .build())
                .build();

        List<CallChain> scoped = MachineScopeFilter.filterCallChainsForMachine(
                List.of(chain),
                "com.example.config.OrderStateMachineConfiguration",
                context,
                List.of(pay, ship));

        assertThat(scoped).hasSize(1);
    }
}
