package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.Tag;
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.Collections;
import java.util.List;

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

@Tag("control_flow_sibling_avoidance")
class GenericEventDetectorControlFlowTest {

    @Test
    void shouldDetectStateFromDirectIfWrapper(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderService {
                    private StateMachine sm;
                    public void processOrder(OrderState state) {
                        if (state == OrderState.PENDING) {
                            sm.sendEvent(OrderEvent.PAY);
                        }
                    }
                }
                
                class StateMachine {
                    public void sendEvent(OrderEvent e) {}
                }
                
                enum OrderState { PENDING }
                enum OrderEvent { PAY }
                """;
        Files.writeString(tempDir.resolve("OrderService.java"), source);
        
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        
        GenericEventDetector detector = new GenericEventDetector(context, new ConstantResolver(), Collections.emptyList());
        List<TriggerPoint> triggers = detector.detect((org.eclipse.jdt.core.dom.CompilationUnit) context.getTypeDeclaration("com.example.OrderService").getRoot());
        
        assertThat(triggers).hasSize(1);
        TriggerPoint tp = triggers.get(0);
        assertThat(tp.getEvent()).isEqualTo("OrderEvent.PAY");
        assertThat(tp.getSourceState()).isEqualTo("PENDING");
    }

    @Test
    void shouldDetectStateFromGuardClauseSibling(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderService {
                    private StateMachine sm;
                    public void processOrder(OrderState state) {
                        if (state != OrderState.PENDING) {
                            return;
                        }
                        
                        sm.sendEvent(OrderEvent.PAY);
                    }
                }
                
                class StateMachine {
                    public void sendEvent(OrderEvent e) {}
                }
                
                enum OrderState { PENDING }
                enum OrderEvent { PAY }
                """;
        Files.writeString(tempDir.resolve("OrderService.java"), source);
        
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        
        GenericEventDetector detector = new GenericEventDetector(context, new ConstantResolver(), Collections.emptyList());
        List<TriggerPoint> triggers = detector.detect((org.eclipse.jdt.core.dom.CompilationUnit) context.getTypeDeclaration("com.example.OrderService").getRoot());
        
        assertThat(triggers).hasSize(1);
        TriggerPoint tp = triggers.get(0);
        assertThat(tp.getEvent()).isEqualTo("OrderEvent.PAY");
        assertThat(tp.getSourceState()).isEqualTo("PENDING");
    }

    @Test
    void shouldDetectStateFromSwitchCaseSibling(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public class OrderService {
                    private StateMachine sm;
                    public void processOrder(OrderState state) {
                        switch (state) {
                            case PENDING:
                                sm.sendEvent(OrderEvent.PAY);
                                break;
                            case PAID:
                                sm.sendEvent(OrderEvent.SHIP);
                                break;
                        }
                    }
                }
                
                class StateMachine {
                    public void sendEvent(OrderEvent e) {}
                }
                
                enum OrderState { PENDING, PAID }
                enum OrderEvent { PAY, SHIP }
                """;
        Files.writeString(tempDir.resolve("OrderService.java"), source);
        
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        
        GenericEventDetector detector = new GenericEventDetector(context, new ConstantResolver(), Collections.emptyList());
        List<TriggerPoint> triggers = detector.detect((org.eclipse.jdt.core.dom.CompilationUnit) context.getTypeDeclaration("com.example.OrderService").getRoot());
        
        assertThat(triggers).hasSize(2);
        
        TriggerPoint payTrigger = triggers.stream().filter(t -> t.getEvent().equals("OrderEvent.PAY")).findFirst().get();
        assertThat(payTrigger.getSourceState()).isEqualTo("PENDING");
        
        TriggerPoint shipTrigger = triggers.stream().filter(t -> t.getEvent().equals("OrderEvent.SHIP")).findFirst().get();
        assertThat(shipTrigger.getSourceState()).isEqualTo("PAID");
    }
}
