package click.kamil.springstatemachineexporter.analysis.pipeline;

import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Files;
import java.nio.file.Path;

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

class ReactiveExpressionSupportTest {

    @Test
    void shouldMapLambdaParameterToOuterJustPayload(@TempDir Path tempDir) throws Exception {
        String source = """
                package com.example;
                public class Runner {
                    Payload payload;
                    void run() {
                        Mono.just(payload).flatMap(p -> Mono.just(p.getEvent()));
                    }
                }
                class Payload { String getEvent() { return "X"; } }
                class Mono {
                    static Mono just(Object o) { return new Mono(); }
                    Mono flatMap(Object fn) { return this; }
                }
                """;
        Path file = tempDir.resolve("Runner.java");
        Files.writeString(file, source);

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

        org.eclipse.jdt.core.dom.TypeDeclaration runner = context.getTypeDeclaration("com.example.Runner");
        CompilationUnit cu = (CompilationUnit) runner.getRoot();
        final MethodInvocation[] flatMapCall = new MethodInvocation[1];
        cu.accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation node) {
                if ("flatMap".equals(node.getName().getIdentifier()) && flatMapCall[0] == null) {
                    flatMapCall[0] = node;
                }
                return super.visit(node);
            }
        });

        String payload = ReactiveExpressionSupport.extractPayload(
                flatMapCall[0], context.getConstantResolver(), context);
        assertThat(payload).isEqualTo("payload.getEvent()");
    }

    @Test
    void shouldRemapLambdaParameterGetter(@TempDir Path tempDir) throws Exception {
        String source = """
                package com.example;
                public class Runner {
                    Payload payload;
                    StateMachine sm;
                    void run() {
                        Mono.just(payload).flatMap(p -> sm.sendEvent(p.getEvent()));
                    }
                }
                class Payload { String getEvent() { return "X"; } }
                class StateMachine { void sendEvent(String e) {} }
                class Mono {
                    static Mono just(Object o) { return new Mono(); }
                    Mono flatMap(Object fn) { return this; }
                }
                """;
        Path file = tempDir.resolve("Runner.java");
        Files.writeString(file, source);

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

        org.eclipse.jdt.core.dom.TypeDeclaration runner = context.getTypeDeclaration("com.example.Runner");
        CompilationUnit cu = (CompilationUnit) runner.getRoot();
        final MethodInvocation[] sendEventCall = new MethodInvocation[1];
        cu.accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation node) {
                if ("sendEvent".equals(node.getName().getIdentifier()) && sendEventCall[0] == null) {
                    sendEventCall[0] = node;
                }
                return super.visit(node);
            }
        });

        Expression eventExpr = (Expression) sendEventCall[0].arguments().get(0);
        String remapped = ReactiveExpressionSupport.remapLambdaParameterGetter(
                eventExpr, context.getConstantResolver(), context);
        assertThat(remapped).isEqualTo("payload.getEvent()");
    }

    @Test
    void shouldRemapLambdaParameterGetterInsideMap(@TempDir Path tempDir) throws Exception {
        String source = """
                package com.example;
                public class Runner {
                    Payload payload;
                    StateMachine sm;
                    void run() {
                        Mono.just(payload).map(p -> p.getEvent());
                    }
                }
                class Payload { String getEvent() { return "X"; } }
                class StateMachine { void sendEvent(String e) {} }
                class Mono {
                    static Mono just(Object o) { return new Mono(); }
                    Mono map(Object fn) { return this; }
                }
                """;
        Path file = tempDir.resolve("Runner.java");
        Files.writeString(file, source);

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

        org.eclipse.jdt.core.dom.TypeDeclaration runner = context.getTypeDeclaration("com.example.Runner");
        CompilationUnit cu = (CompilationUnit) runner.getRoot();
        final MethodInvocation[] getEventCall = new MethodInvocation[1];
        cu.accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation node) {
                if ("getEvent".equals(node.getName().getIdentifier()) && getEventCall[0] == null) {
                    getEventCall[0] = node;
                }
                return super.visit(node);
            }
        });

        String remapped = ReactiveExpressionSupport.remapLambdaParameterGetter(
                getEventCall[0], context.getConstantResolver(), context);
        assertThat(remapped).isEqualTo("payload.getEvent()");
    }
}
