package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
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 static org.assertj.core.api.Assertions.assertThat;

class TypeResolverTest {

    @Test
    void shouldMapGenericEventPlaceholderToSingleParameterIndex(@TempDir Path tempDir) throws Exception {
        Files.writeString(tempDir.resolve("Machine.java"), """
                package com.example;
                class StateMachine {
                    void sendEvent(OrderEvent event) {}
                }
                enum OrderEvent { PAY }
                """);

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

        assertThat(resolver.getParameterIndex("com.example.StateMachine.sendEvent", "event")).isZero();
        assertThat(resolver.getParameterIndex("com.example.StateMachine.sendEvent", "e", true)).isZero();
        assertThat(resolver.getParameterIndex("com.example.StateMachine.sendEvent", "e")).isEqualTo(-1);
        assertThat(resolver.getParameterIndex("com.example.StateMachine.sendEvent", "payload")).isEqualTo(-1);
    }

    @Test
    void shouldFailClosedOnAmbiguousSimpleTypeCompatibility(@TempDir Path tempDir) throws Exception {
        Files.createDirectories(tempDir.resolve("a"));
        Files.createDirectories(tempDir.resolve("b"));
        Files.writeString(tempDir.resolve("a/OrderEvent.java"),
                "package a; public enum OrderEvent { PAY }");
        Files.writeString(tempDir.resolve("b/OrderEvent.java"),
                "package b; public enum OrderEvent { SHIP }");

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

        assertThat(resolver.isTypeCompatible("a.OrderEvent", "OrderEvent")).isFalse();
        assertThat(resolver.isTypeCompatible("a.OrderEvent", "b.OrderEvent")).isFalse();
        assertThat(resolver.isTypeCompatible("a.OrderEvent", "a.OrderEvent")).isTrue();
    }

    @Test
    void shouldUpgradeSimpleHeuristicTypeToBindingFqnForUserTypes(@TempDir Path tempDir) throws Exception {
        Files.createDirectories(tempDir.resolve("com/example/order"));
        Files.writeString(tempDir.resolve("com/example/order/OrderEvent.java"), """
                package com.example.order;
                public enum OrderEvent { PAY }
                """);
        Files.writeString(tempDir.resolve("App.java"), """
                package com.example;
                import com.example.order.OrderEvent;
                class StateMachine {
                    void sendEvent(OrderEvent e) {}
                }
                """);

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

        TypeDeclaration td = context.getTypeDeclaration("com.example.StateMachine");
        MethodDeclaration md = context.findMethodDeclaration(td, "sendEvent", true);
        SingleVariableDeclaration param = (SingleVariableDeclaration) md.parameters().get(0);

        assertThat(resolver.resolveTypeToFqn(param.getType(), (CompilationUnit) td.getRoot()))
                .isEqualTo("com.example.order.OrderEvent");
    }

    @Test
    void shouldKeepHeuristicStringTypeWhenBindingsWouldReturnJavaLangString(@TempDir Path tempDir) throws Exception {
        Files.writeString(tempDir.resolve("App.java"), """
                package com.example;
                class Api {
                    void handle(String value) {}
                }
                """);

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

        TypeDeclaration td = context.getTypeDeclaration("com.example.Api");
        MethodDeclaration md = context.findMethodDeclaration(td, "handle", true);
        SingleVariableDeclaration param = (SingleVariableDeclaration) md.parameters().get(0);

        assertThat(resolver.resolveTypeToFqn(param.getType(), (CompilationUnit) td.getRoot()))
                .isEqualTo("String");
    }
}
