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.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.service.CodebaseIntelligenceProvider;
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.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;

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

class EnricherPipelineRegressionTest {

    @Test
    void shouldCanonicalizeChainTriggerEventsBeforeLinker(@TempDir Path tempDir) throws IOException {
        writeSampleMachine(tempDir);

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

        TriggerPoint rawTrigger = TriggerPoint.builder()
                .className("com.example.web.OrderController")
                .methodName("pay")
                .event("OrderEvent.PAY")
                .sourceState("OrderState.NEW")
                .eventTypeFqn("OrderEvent")
                .stateTypeFqn("OrderState")
                .build();

        CallChain rawChain = CallChain.builder()
                .entryPoint(EntryPoint.builder()
                        .type(EntryPoint.Type.REST)
                        .name("POST /api/orders/pay")
                        .className("com.example.web.OrderController")
                        .methodName("pay")
                        .build())
                .triggerPoint(rawTrigger)
                .build();

        CodebaseIntelligenceProvider intelligence = new CodebaseIntelligenceProvider() {
            @Override
            public List<TriggerPoint> findTriggerPoints() {
                return List.of(rawTrigger);
            }

            @Override
            public List<EntryPoint> findEntryPoints() {
                return List.of();
            }

            @Override
            public List<CallChain> findCallChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers) {
                return List.of(rawChain);
            }

            @Override
            public Map<String, Map<String, String>> resolveProperties() {
                return Map.of();
            }
        };

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

        AnalysisResult result = AnalysisResult.builder()
                .name("com.example.config.OrderStateMachineConfiguration")
                .transitions(List.of(transition))
                .metadata(CodebaseMetadata.builder()
                        .entryPoints(List.of(rawChain.getEntryPoint()))
                        .triggers(List.of(rawTrigger))
                        .callChains(List.of(rawChain))
                        .build())
                .build();

        new TriggerCanonicalizationEnricher().enrich(result, context, intelligence);
        new TransitionLinkerEnricher().enrich(result, context, intelligence);

        TriggerPoint chainTrigger = result.getMetadata().getCallChains().get(0).getTriggerPoint();
        assertThat(chainTrigger.getEvent()).isEqualTo("com.example.order.OrderEvent.PAY");
        assertThat(chainTrigger.getSourceState()).isEqualTo("com.example.order.OrderState.NEW");
        assertThat(result.getMetadata().getTriggers().get(0).getEvent())
                .isEqualTo("com.example.order.OrderEvent.PAY");

        assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(1);
        assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions().get(0).getEvent())
                .isEqualTo("com.example.order.OrderEvent.PAY");
    }

    private static void writeSampleMachine(Path tempDir) throws IOException {
        Path orderPkg = tempDir.resolve("com/example/order");
        Path configPkg = tempDir.resolve("com/example/config");
        Path webPkg = tempDir.resolve("com/example/web");
        Files.createDirectories(orderPkg);
        Files.createDirectories(configPkg);
        Files.createDirectories(webPkg);

        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> {
                }
                """);
        Files.writeString(webPkg.resolve("OrderController.java"),
                """
                package com.example.web;
                public class OrderController {
                    public void pay() {}
                }
                """);
    }
}
