package click.kamil.springstatemachineexporter.analysis.index;

import click.kamil.springstatemachineexporter.analysis.pipeline.AccessorResolver;
import click.kamil.springstatemachineexporter.analysis.pipeline.ResolutionBudget;
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.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.List;

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

class GetterChainIndexTest {

    @TempDir
    Path tempDir;

    CodebaseContext context;
    AccessorResolver accessorResolver;

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

    @Test
    void shouldPrecomputeGetterChainEdgeWithFieldInitializerCic() throws IOException {
        writeJava("com/example/Payload.java", """
                package com.example;
                public class Payload {
                    public String getType() { return "PAY"; }
                }
                """);
        writeJava("com/example/Inner.java", """
                package com.example;
                public class Inner {
                    private final Payload payload = new Payload();
                    public Payload getPayload() { return payload; }
                }
                """);
        scan();

        GetterChainIndex index = context.getGetterChainIndex();
        assertThat(index.edgeCount()).isGreaterThan(0);

        GetterChainEdge edge = index.lookup("com.example.Inner", "getPayload").orElseThrow();
        assertThat(edge.nextTypeFqn()).contains("Payload");
        assertThat(edge.fieldInitializerCic()).isNotNull();

        AccessorResolver.GetterChainStep step = accessorResolver.resolveGetterChainStep(
                "com.example.Inner", "getPayload", ResolutionBudget.defaults());
        assertThat(step).isNotNull();
        assertThat(step.typeFqn()).isEqualTo(edge.nextTypeFqn());
        assertThat(step.cic()).isSameAs(edge.fieldInitializerCic());
    }

    @Test
    void getTypeDeclarationShouldUseFlatIndex() throws IOException {
        writeJava("com/example/Demo.java", """
                package com.example;
                public class Demo {
                    public String getValue() { return "X"; }
                }
                """);
        scan();

        TypeDeclaration first = context.getTypeDeclaration("com.example.Demo");
        TypeDeclaration second = context.getTypeDeclaration("com.example.Demo");

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

    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);
    }
}
