package click.kamil.springstatemachineexporter.analysis.service;

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 GenericEventDetectorFireTest {

    @Test
    void shouldDetectFireMethodAsTrigger(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            enum OrderEvent { PAY, SHIP }
            class OrderService {
                private final StateMachineFacade facade;
                OrderService(StateMachineFacade facade) { this.facade = facade; }
                public void pay() {
                    facade.fire(OrderEvent.PAY);
                }
            }
            class StateMachineFacade {
                public void fire(OrderEvent event) {}
            }
            """;
        Files.writeString(tempDir.resolve("OrderService.java"), source);

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

        GenericEventDetector detector = new GenericEventDetector(context, context.getConstantResolver(), List.of());
        org.eclipse.jdt.core.dom.TypeDeclaration td = context.getTypeDeclaration("com.example.OrderService");
        List<TriggerPoint> triggers = detector.detect((org.eclipse.jdt.core.dom.CompilationUnit) td.getRoot());

        assertThat(triggers).hasSize(1);
        assertThat(triggers.get(0).getMethodName()).isEqualTo("pay");
        assertThat(triggers.get(0).getEvent()).contains("PAY");
    }

    @Test
    void shouldInferSourceStateFromNonGuardIfLiteralBranch(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            enum OrderState { NEW, PAID }
            enum OrderEvent { PAY }
            class OrderService {
                private final StateMachineFacade facade;
                OrderService(StateMachineFacade facade) { this.facade = facade; }
                public void payWhenPaid(OrderState state) {
                    if (state == OrderState.PAID) {
                        facade.sendEvent(OrderEvent.PAY);
                    }
                }
            }
            class StateMachineFacade {
                public void sendEvent(OrderEvent event) {}
            }
            """;
        Files.writeString(tempDir.resolve("OrderService.java"), source);

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

        GenericEventDetector detector = new GenericEventDetector(context, context.getConstantResolver(), List.of());
        org.eclipse.jdt.core.dom.TypeDeclaration td = context.getTypeDeclaration("com.example.OrderService");
        List<TriggerPoint> triggers = detector.detect((org.eclipse.jdt.core.dom.CompilationUnit) td.getRoot());

        assertThat(triggers).hasSize(1);
        assertThat(triggers.get(0).getSourceState()).isEqualTo("PAID");
    }

    @Test
    void shouldInferStateMachineIdFromQualifierOnSendEventReceiver(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            enum OrderEvent { PAY }
            class OrderService {
                @org.springframework.beans.factory.annotation.Qualifier("paymentStateMachine")
                private final StateMachine stateMachine;
                OrderService(StateMachine stateMachine) { this.stateMachine = stateMachine; }
                public void pay() {
                    stateMachine.sendEvent(OrderEvent.PAY);
                }
            }
            class StateMachine {
                public void sendEvent(OrderEvent event) {}
            }
            """;
        Files.writeString(tempDir.resolve("OrderService.java"), source);

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

        GenericEventDetector detector = new GenericEventDetector(context, context.getConstantResolver(), List.of());
        org.eclipse.jdt.core.dom.TypeDeclaration td = context.getTypeDeclaration("com.example.OrderService");
        List<TriggerPoint> triggers = detector.detect((org.eclipse.jdt.core.dom.CompilationUnit) td.getRoot());

        assertThat(triggers).hasSize(1);
        assertThat(triggers.get(0).getStateMachineId()).isEqualTo("paymentStateMachine");
    }

    @Test
    void shouldInferStateMachineIdFromQualifierOnAutowiredSetter(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            enum OrderEvent { PAY }
            class OrderService {
                private StateMachine stateMachine;
                @org.springframework.beans.factory.annotation.Autowired
                public void setStateMachine(
                        @org.springframework.beans.factory.annotation.Qualifier("paymentStateMachine")
                        StateMachine stateMachine) {
                    this.stateMachine = stateMachine;
                }
                public void pay() {
                    stateMachine.sendEvent(OrderEvent.PAY);
                }
            }
            class StateMachine {
                public void sendEvent(OrderEvent event) {}
            }
            """;
        Files.writeString(tempDir.resolve("OrderService.java"), source);

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

        GenericEventDetector detector = new GenericEventDetector(context, context.getConstantResolver(), List.of());
        org.eclipse.jdt.core.dom.TypeDeclaration td = context.getTypeDeclaration("com.example.OrderService");
        List<TriggerPoint> triggers = detector.detect((org.eclipse.jdt.core.dom.CompilationUnit) td.getRoot());

        assertThat(triggers).hasSize(1);
        assertThat(triggers.get(0).getStateMachineId()).isEqualTo("paymentStateMachine");
    }

    @Test
    void shouldNotInferSourceStateFromVariableOnlyIfCondition(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            enum OrderState { NEW, PAID }
            enum OrderEvent { PAY }
            class OrderService {
                private final StateMachineFacade facade;
                OrderService(StateMachineFacade facade) { this.facade = facade; }
                public void payWhenPaid(boolean isPaid) {
                    if (isPaid) {
                        facade.sendEvent(OrderEvent.PAY);
                    }
                }
            }
            class StateMachineFacade {
                public void sendEvent(OrderEvent event) {}
            }
            """;
        Files.writeString(tempDir.resolve("OrderService.java"), source);

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

        GenericEventDetector detector = new GenericEventDetector(context, context.getConstantResolver(), List.of());
        org.eclipse.jdt.core.dom.TypeDeclaration td = context.getTypeDeclaration("com.example.OrderService");
        List<TriggerPoint> triggers = detector.detect((org.eclipse.jdt.core.dom.CompilationUnit) td.getRoot());

        assertThat(triggers).hasSize(1);
        assertThat(triggers.get(0).getSourceState()).isNull();
    }
}
