package click.kamil.springstatemachineexporter.analysis.index;

import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.analysis.service.ConstructorAnalyzer;
import click.kamil.springstatemachineexporter.analysis.service.ConstantExtractor;
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.BeforeEach;
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.HashSet;
import java.util.List;

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

class AnalysisPerformanceCacheTest {

    @TempDir
    Path tempDir;

    CodebaseContext context;
    ConstantExtractor constantExtractor;

    @BeforeEach
    void setUp() throws IOException {
        context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        ConstantResolver constantResolver = context.getConstantResolver();
        constantExtractor = new ConstantExtractor(context, constantResolver, mi -> null);
        constantExtractor.setConstructorAnalyzer(new ConstructorAnalyzer(context, constantResolver));
    }

    @Test
    void shouldPrecomputeIndexedFieldConstants() throws IOException {
        writeJava("com/example/Payload.java", """
                package com.example;
                public class Payload {
                    private String event = "PAY";
                    public String getEvent() { return event; }
                }
                """);
        scan();

        List<String> precomputed = context.getIndexedFieldConstantsIndex()
                .lookup("com.example.Payload", "getEvent")
                .orElseThrow();
        assertThat(precomputed).containsExactly("PAY");

        List<String> first = constantExtractor.resolveMethodReturnConstant(
                "com.example.Payload", "getEvent", 0, new HashSet<>(), null);
        List<String> second = constantExtractor.resolveMethodReturnConstant(
                "com.example.Payload", "getEvent", 0, new HashSet<>(), null);
        assertThat(first).containsExactly("PAY");
        assertThat(second).isEqualTo(first);
    }

    @Test
    void findMethodDeclarationShouldBeMemoized() throws IOException {
        writeJava("com/example/Demo.java", """
                package com.example;
                public class Demo {
                    public void run() {}
                }
                """);
        scan();

        TypeDeclaration type = context.getTypeDeclaration("com.example.Demo");
        MethodDeclaration first = context.findMethodDeclaration(type, "run", true);
        MethodDeclaration second = context.findMethodDeclaration(type, "run", true);

        assertThat(first).isNotNull();
        assertThat(second).isSameAs(first);
    }

    @Test
    void shouldMemoizePolymorphicClassCompatibility() throws IOException {
        writeJava("com/example/EventSource.java", """
                package com.example;
                public interface EventSource {}
                """);
        writeJava("com/example/PayEvent.java", """
                package com.example;
                public class PayEvent implements EventSource {}
                """);
        scan();

        assertThat(context.areClassesPolymorphicallyCompatible(
                "com.example.PayEvent", "com.example.EventSource")).isTrue();
        assertThat(context.areClassesPolymorphicallyCompatible(
                "com.example.PayEvent", "com.example.EventSource")).isTrue();
    }

    private void scan() throws IOException {
        context.scan(tempDir);
    }

    private void writeJava(String relativePath, String source) throws IOException {
        Path file = tempDir.resolve(relativePath);
        Files.createDirectories(file.getParent());
        Files.writeString(file, source);
    }
}
