package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
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 java.util.Map;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;

class AnalysisResultFinalizerTest {

    @Test
    void shouldReCanonicalizeAfterPropertyResolution(@TempDir Path tempDir) throws Exception {
        writeSampleConfig(tempDir);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

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

        AnalysisResult result = AnalysisResult.builder()
                .name("com.example.config.OrderStateMachineConfiguration")
                .transitions(List.of(transition))
                .startStates(Set.of("OrderState.NEW"))
                .metadata(CodebaseMetadata.builder()
                        .triggers(List.of(TriggerPoint.builder()
                                .event("OrderEvent.PAY")
                                .className("com.example.web.OrderController")
                                .methodName("pay")
                                .sourceFile("OrderController.java")
                                .polymorphicEvents(List.of("OrderEvent.PAY"))
                                .eventTypeFqn("com.example.order.OrderEvent")
                                .build()))
                        .properties(Map.of("default", Map.of()))
                        .build())
                .build();

        result.applyResolution(Map.of());
        AnalysisResultFinalizer.finalizeResult(result, context);

        assertThat(result.getTransitions().get(0).getEvent().fullIdentifier())
                .isEqualTo("com.example.order.OrderEvent.PAY");
        assertThat(result.getStartStates())
                .containsExactly("com.example.order.OrderState.NEW");
        assertThat(result.getMetadata().getTriggers().get(0).getPolymorphicEvents())
                .containsExactly("com.example.order.OrderEvent.PAY");
        assertThat(result.getStateTypeFqn()).isEqualTo("com.example.order.OrderState");
        assertThat(result.getEventTypeFqn()).isEqualTo("com.example.order.OrderEvent");
    }

    @Test
    void shouldCanonicalizeUsingEmbeddedMachineTypesWithoutContext() {
        Transition transition = new Transition();
        transition.setEvent(Event.of("OrderEvent.PAY", "OrderEvent.PAY"));
        transition.setSourceStates(List.of(State.of("OrderState.NEW", "OrderState.NEW")));
        transition.setTargetStates(List.of(State.of("OrderState.PAID", "OrderState.PAID")));

        AnalysisResult result = AnalysisResult.builder()
                .name("com.example.config.OrderStateMachineConfiguration")
                .stateTypeFqn("com.example.order.OrderState")
                .eventTypeFqn("com.example.order.OrderEvent")
                .transitions(List.of(transition))
                .startStates(Set.of("OrderState.NEW"))
                .build();

        AnalysisResultFinalizer.finalizeResult(
                result,
                null,
                new click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver.MachineTypes(
                        "com.example.order.OrderState",
                        "com.example.order.OrderEvent"));

        assertThat(result.getTransitions().get(0).getEvent().fullIdentifier())
                .isEqualTo("com.example.order.OrderEvent.PAY");
        assertThat(result.getStartStates())
                .containsExactly("com.example.order.OrderState.NEW");
    }

    @Test
    void applyResolutionShouldRebuildTriggersWithAllFields(@TempDir Path tempDir) throws Exception {
        writeSampleConfig(tempDir);

        TriggerPoint trigger = TriggerPoint.builder()
                .event("${app.event}")
                .sourceState("${app.state}")
                .className("com.example.web.OrderController")
                .methodName("pay")
                .sourceFile("OrderController.java")
                .polymorphicEvents(List.of("${app.event}"))
                .eventTypeFqn("com.example.order.OrderEvent")
                .stateTypeFqn("com.example.order.OrderState")
                .build();

        AnalysisResult result = AnalysisResult.builder()
                .name("com.example.config.OrderStateMachineConfiguration")
                .metadata(CodebaseMetadata.builder().triggers(List.of(trigger)).build())
                .build();

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        result.applyResolution(Map.of(
                "app.event", "OrderEvent.PAY",
                "app.state", "OrderState.NEW"), context);

        TriggerPoint resolved = result.getMetadata().getTriggers().get(0);
        assertThat(resolved.getEvent()).isEqualTo("com.example.order.OrderEvent.PAY");
        assertThat(resolved.getSourceState()).isEqualTo("com.example.order.OrderState.NEW");
        assertThat(resolved.getPolymorphicEvents()).containsExactly("com.example.order.OrderEvent.PAY");
    }

    @Test
    void shouldCanonicalizeAmbiguousImportStyleMatchedTransitions(@TempDir Path tempDir) throws Exception {
        Files.createDirectories(tempDir.resolve("a"));
        Files.createDirectories(tempDir.resolve("b"));
        Files.createDirectories(tempDir.resolve("com/example/config"));
        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 }");
        Files.writeString(tempDir.resolve("a/OrderState.java"), "package a; public enum OrderState { NEW, PAID }");
        Files.writeString(tempDir.resolve("com/example/config/OrderStateMachineConfiguration.java"),
                """
                package com.example.config;
                import a.OrderEvent;
                import a.OrderState;
                import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
                public class OrderStateMachineConfiguration
                        extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {
                }
                """);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        AnalysisResult result = AnalysisResult.builder()
                .name("com.example.config.OrderStateMachineConfiguration")
                .transitions(List.of())
                .metadata(CodebaseMetadata.builder()
                        .callChains(List.of(click.kamil.springstatemachineexporter.analysis.model.CallChain.builder()
                                .matchedTransitions(List.of(
                                        click.kamil.springstatemachineexporter.analysis.model.MatchedTransition.builder()
                                                .event("OrderEvent.PAY")
                                                .sourceState("a.OrderState.NEW")
                                                .targetState("a.OrderState.PAID")
                                                .build()))
                                .build()))
                        .build())
                .build();

        AnalysisResultFinalizer.applyCanonicalization(result, context,
                new click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver.MachineTypes(
                        "a.OrderState", "a.OrderEvent"));

        assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions().get(0).getEvent())
                .isEqualTo("a.OrderEvent.PAY");
    }

    private static void writeSampleConfig(Path tempDir) throws Exception {
        Path orderPkg = tempDir.resolve("com/example/order");
        Path configPkg = tempDir.resolve("com/example/config");
        Files.createDirectories(orderPkg);
        Files.createDirectories(configPkg);
        Files.writeString(orderPkg.resolve("OrderState.java"),
                "package com.example.order; public enum OrderState { NEW, PAID }");
        Files.writeString(orderPkg.resolve("OrderEvent.java"),
                "package com.example.order; public enum OrderEvent { PAY }");
        Files.writeString(configPkg.resolve("OrderStateMachineConfiguration.java"),
                """
                package com.example.config;
                import com.example.order.OrderEvent;
                import com.example.order.OrderState;
                import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
                public class OrderStateMachineConfiguration
                        extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {
                }
                """);
    }
}
