package click.kamil.springstatemachineexporter.analysis.pipeline;

import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.TypeDeclaration;
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.Optional;

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

class EventArgumentSelectorTest {

    @Test
    void singleArgIsSelected(@TempDir Path tempDir) throws Exception {
        MethodInvocation call = sendEventCall(tempDir, """
                package com.example;
                class C {
                  StateMachine sm;
                  void fire(OrderEvent e) {
                    sm.sendEvent(e);
                  }
                }
                class StateMachine { void sendEvent(OrderEvent e) {} }
                enum OrderEvent { PAY }
                """);
        Optional<Expression> selected = EventArgumentSelector.selectEventArgument(call);
        assertThat(selected).isPresent();
        assertThat(selected.get().toString()).isEqualTo("e");
        assertThat(EventArgumentSelector.selectedIsFirstArgument(call, selected.get())).isTrue();
    }

    @Test
    void eventThenStateSelectsEvent(@TempDir Path tempDir) throws Exception {
        MethodInvocation call = sendEventCall(tempDir, """
                package com.example;
                class C {
                  StateMachine sm;
                  void fire() {
                    sm.sendEvent(OrderEvent.PAY, OrderState.PENDING);
                  }
                }
                class StateMachine { void sendEvent(OrderEvent e, OrderState s) {} }
                enum OrderEvent { PAY }
                enum OrderState { PENDING }
                """);
        Optional<Expression> selected = EventArgumentSelector.selectEventArgument(call);
        assertThat(selected).isPresent();
        assertThat(selected.get().toString()).contains("OrderEvent.PAY");
        assertThat(EventArgumentSelector.selectedIsFirstArgument(call, selected.get())).isTrue();
    }

    @Test
    void headersThenEventSelectsEvent(@TempDir Path tempDir) throws Exception {
        MethodInvocation call = sendEventCall(tempDir, """
                package com.example;
                import java.util.Map;
                class C {
                  StateMachine sm;
                  void fire(Map<String, Object> headers, String code) {
                    sm.sendEvent(headers, OrderEvent.valueOf(code));
                  }
                }
                class StateMachine { void sendEvent(Map<String, Object> headers, OrderEvent e) {} }
                enum OrderEvent { PAY, SHIP }
                """);
        Optional<Expression> selected = EventArgumentSelector.selectEventArgument(call);
        assertThat(selected).isPresent();
        assertThat(selected.get().toString()).contains("valueOf");
        assertThat(EventArgumentSelector.selectedIsFirstArgument(call, selected.get())).isFalse();
    }

    @Test
    void ambiguousTwoArgsStayEmpty(@TempDir Path tempDir) throws Exception {
        MethodInvocation call = sendEventCall(tempDir, """
                package com.example;
                class C {
                  StateMachine sm;
                  void fire(String a, String b) {
                    sm.sendEvent(a, b);
                  }
                }
                class StateMachine { void sendEvent(String a, String b) {} }
                """);
        assertThat(EventArgumentSelector.selectEventArgument(call)).isEmpty();
    }

    @Test
    void zeroArgsStayEmpty(@TempDir Path tempDir) throws Exception {
        MethodInvocation call = sendEventCall(tempDir, """
                package com.example;
                class C {
                  StateMachine sm;
                  void fire() {
                    sm.sendEvent();
                  }
                }
                class StateMachine { void sendEvent() {} }
                """);
        assertThat(EventArgumentSelector.selectEventArgument(call)).isEmpty();
    }

    private static MethodInvocation sendEventCall(Path tempDir, String source) throws Exception {
        Files.writeString(tempDir.resolve("C.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        TypeDeclaration td = context.getTypeDeclaration("com.example.C");
        if (td == null) {
            td = context.getTypeDeclaration("C");
        }
        assertThat(td).isNotNull();
        MethodDeclaration md = context.findMethodDeclaration(td, "fire", true);
        assertThat(md).isNotNull();
        MethodInvocation[] found = {null};
        md.accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation node) {
                if ("sendEvent".equals(node.getName().getIdentifier())) {
                    found[0] = node;
                    return false;
                }
                return super.visit(node);
            }
        });
        assertThat(found[0]).isNotNull();
        return found[0];
    }
}
