package click.kamil.springstatemachineexporter.analysis.flow;

import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.MethodDeclaration;
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.List;
import java.util.Optional;

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

class CallSiteArgBinderTest {

    @Test
    void uniqueSiteWithoutEdgeText(@TempDir Path tempDir) throws Exception {
        MethodDeclaration caller = scanCaller(tempDir, """
                package com.example;
                class C {
                  void fire(String a) { route(a); }
                  void route(String code) {}
                }
                """);
        CallEdge edge = new CallEdge("com.example.C.route", List.of("a"));
        Optional<?> arg = CallSiteArgBinder.findUniqueArgument(caller, edge, 0);
        assertThat(arg).isPresent();
        assertThat(arg.get().toString()).isEqualTo("a");
    }

    @Test
    void multiSiteUsesEdgeText(@TempDir Path tempDir) throws Exception {
        MethodDeclaration caller = scanCaller(tempDir, """
                package com.example;
                class C {
                  void fire(String a, String b) {
                    route(a);
                    route(b);
                  }
                  void route(String code) {}
                }
                """);
        CallEdge edge = new CallEdge("com.example.C.route", List.of("b"));
        Optional<?> arg = CallSiteArgBinder.findUniqueArgument(caller, edge, 0);
        assertThat(arg).isPresent();
        assertThat(arg.get().toString()).isEqualTo("b");
    }

    @Test
    void multiSiteIdenticalTextPicksFirst(@TempDir Path tempDir) throws Exception {
        MethodDeclaration caller = scanCaller(tempDir, """
                package com.example;
                class C {
                  void fire(String a) {
                    route(a);
                    route(a);
                  }
                  void route(String code) {}
                }
                """);
        CallEdge edge = new CallEdge("com.example.C.route", List.of("a"));
        Optional<?> arg = CallSiteArgBinder.findUniqueArgument(caller, edge, 0);
        assertThat(arg).isPresent();
        assertThat(arg.get().toString()).isEqualTo("a");
    }

    @Test
    void multiSiteWithoutEdgeTextFailsClosed(@TempDir Path tempDir) throws Exception {
        MethodDeclaration caller = scanCaller(tempDir, """
                package com.example;
                class C {
                  void fire(String a, String b) {
                    route(a);
                    route(b);
                  }
                  void route(String code) {}
                }
                """);
        CallEdge edge = new CallEdge("com.example.C.route", null);
        assertThat(CallSiteArgBinder.findUniqueArgument(caller, edge, 0)).isEmpty();
    }

    private static MethodDeclaration scanCaller(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 fire = context.findMethodDeclaration(td, "fire", true);
        assertThat(fire).isNotNull();
        return fire;
    }
}
