package click.kamil.springstatemachineexporter.analysis.enricher.routing;

import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
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;

class SpringInjectionRoutingTest {

    @Test
    void shouldRouteByQualifierWithoutPackageNameHeuristics(@TempDir Path tempDir) throws Exception {
        Files.writeString(tempDir.resolve("PaymentService.java"), """
                package com.example;
                import org.springframework.beans.factory.annotation.Qualifier;
                import org.springframework.statemachine.StateMachine;
                import org.springframework.stereotype.Service;
                @Service
                public class PaymentService {
                    @Qualifier("paymentStateMachine")
                    private final StateMachine<String, String> stateMachine;
                    public PaymentService(StateMachine<String, String> stateMachine) {
                        this.stateMachine = stateMachine;
                    }
                    public void capture() {
                        stateMachine.sendEvent("CAPTURE");
                    }
                }
                @org.springframework.context.annotation.Configuration
                @org.springframework.statemachine.config.EnableStateMachine(name = "paymentStateMachine")
                class PaymentStateMachineConfig
                        extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
                @org.springframework.context.annotation.Configuration
                @org.springframework.statemachine.config.EnableStateMachine(name = "orderStateMachine")
                class OrderStateMachineConfig
                        extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
                """);

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

        HeuristicBeanResolutionEngine engine = new HeuristicBeanResolutionEngine();
        CallChain chain = CallChain.builder()
                .triggerPoint(TriggerPoint.builder()
                        .className("com.example.PaymentService")
                        .methodName("capture")
                        .lineNumber(12)
                        .event("CAPTURE")
                        .eventTypeFqn("java.lang.String")
                        .stateTypeFqn("java.lang.String")
                        .stateMachineId("paymentStateMachine")
                        .build())
                .methodChain(List.of("com.example.PaymentService.capture()"))
                .build();

        assertThat(engine.isRoutedToCorrectMachine(
                chain, "com.example.PaymentStateMachineConfig", context)).isTrue();
        assertThat(engine.isRoutedToCorrectMachine(
                chain, "com.example.OrderStateMachineConfig", context)).isFalse();
    }

    @Test
    void shouldNotBorrowConstructorQualifierFromUnrelatedParameter(@TempDir Path tempDir) throws Exception {
        Files.writeString(tempDir.resolve("PaymentService.java"), """
                package com.example;
                import org.springframework.beans.factory.annotation.Qualifier;
                import org.springframework.statemachine.StateMachine;
                import org.springframework.stereotype.Service;
                @Service
                public class PaymentService {
                    private final StateMachine<String, String> orderMachine;
                    private final StateMachine<String, String> paymentMachine;
                    public PaymentService(StateMachine<String, String> orderMachine,
                                          @Qualifier("paymentStateMachine") StateMachine<String, String> paymentMachine) {
                        this.orderMachine = orderMachine;
                        this.paymentMachine = paymentMachine;
                    }
                    public void captureOnOrder() {
                        orderMachine.sendEvent("CAPTURE");
                    }
                }
                @org.springframework.context.annotation.Configuration
                @org.springframework.statemachine.config.EnableStateMachine(name = "paymentStateMachine")
                class PaymentStateMachineConfig
                        extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
                @org.springframework.context.annotation.Configuration
                @org.springframework.statemachine.config.EnableStateMachine(name = "orderStateMachine")
                class OrderStateMachineConfig
                        extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
                """);

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

        HeuristicBeanResolutionEngine engine = new HeuristicBeanResolutionEngine();
        CallChain chain = CallChain.builder()
                .triggerPoint(TriggerPoint.builder()
                        .className("com.example.PaymentService")
                        .methodName("captureOnOrder")
                        .lineNumber(18)
                        .event("CAPTURE")
                        .eventTypeFqn("java.lang.String")
                        .stateTypeFqn("java.lang.String")
                        .build())
                .methodChain(List.of("com.example.PaymentService.captureOnOrder()"))
                .build();

        // Both are String,String machines and receiver has no explicit qualifier -> should fail closed.
        assertThat(engine.isRoutedToCorrectMachine(chain, "com.example.PaymentStateMachineConfig", context)).isFalse();
        assertThat(engine.isRoutedToCorrectMachine(chain, "com.example.OrderStateMachineConfig", context)).isFalse();
    }

    @Test
    void shouldFailClosedForAmbiguousStringStateMachineWithoutQualifier(@TempDir Path tempDir) throws Exception {
        Files.writeString(tempDir.resolve("OrderService.java"), """
                package com.example;
                import org.springframework.statemachine.StateMachine;
                import org.springframework.stereotype.Service;
                import lombok.RequiredArgsConstructor;
                @Service
                @RequiredArgsConstructor
                public class OrderService {
                    private final StateMachine<String, String> stateMachine;
                    public void finish() {
                        stateMachine.sendEvent("FINISH");
                    }
                }
                @org.springframework.context.annotation.Configuration
                @org.springframework.statemachine.config.EnableStateMachine(name = "machineA")
                class MachineAConfig
                        extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
                @org.springframework.context.annotation.Configuration
                @org.springframework.statemachine.config.EnableStateMachine(name = "machineB")
                class MachineBConfig
                        extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
                """);

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

        HeuristicBeanResolutionEngine engine = new HeuristicBeanResolutionEngine();
        CallChain chain = CallChain.builder()
                .triggerPoint(TriggerPoint.builder()
                        .className("com.example.OrderService")
                        .methodName("finish")
                        .lineNumber(11)
                        .event("FINISH")
                        .eventTypeFqn("java.lang.String")
                        .stateTypeFqn("java.lang.String")
                        .build())
                .methodChain(List.of("com.example.OrderService.finish()"))
                .build();

        assertThat(engine.isRoutedToCorrectMachine(chain, "com.example.MachineAConfig", context)).isFalse();
        assertThat(engine.isRoutedToCorrectMachine(chain, "com.example.MachineBConfig", context)).isFalse();
    }
}
