package click.kamil.springstatemachineexporter.analysis.index;

import click.kamil.springstatemachineexporter.analysis.service.ConstructorAnalyzer;
import click.kamil.springstatemachineexporter.analysis.service.ConstantExtractor;
import click.kamil.springstatemachineexporter.analysis.service.VariableTracer;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.Expression;
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 ConstantExtractorAccessorInliningTest {

    private CodebaseContext context;
    private ConstantExtractor constantExtractor;
    private VariableTracer variableTracer;

    @BeforeEach
    void setUp(@TempDir Path tempDir) throws IOException {
        writeJava(tempDir, "com/example/Payload.java", """
                package com.example;
                public class Payload {
                    private String event = "SHIP";
                    public String getEvent() { return event; }
                    public void setEvent(String event) { this.event = event; }
                }
                """);
        writeJava(tempDir, "com/example/Runner.java", """
                package com.example;
                public class Runner {
                    public void run() {
                        Payload payload = new Payload();
                        payload.setEvent("PAY");
                        String value = payload.getEvent();
                    }
                }
                """);

        context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);

        constantExtractor = new ConstantExtractor(context, context.getConstantResolver(), mi -> null);
        variableTracer = new VariableTracer(context, context.getConstantResolver());
        constantExtractor.setVariableTracer(variableTracer);
        constantExtractor.setConstructorAnalyzer(new ConstructorAnalyzer(context, context.getConstantResolver()));
    }

    @Test
    void resolveMethodReturnConstantShouldReadIndexedGetterField() {
        List<String> constants = constantExtractor.resolveMethodReturnConstant(
                "com.example.Payload", "getEvent", 0, new HashSet<>(), null);

        assertThat(constants).contains("SHIP");
    }

    @Test
    void traceLocalSetterShouldUseAccessorIndexFieldName() {
        Expression result = variableTracer.traceLocalSetter(
                "com.example.Runner.run", "payload", "getEvent");

        assertThat(result).isNotNull();
        assertThat(result.toString()).contains("PAY");
    }

    @Test
    void indexedGetterShouldExistForPayload() {
        assertThat(context.getAccessorIndex().lookup("com.example.Payload", "getEvent")).isPresent();
        assertThat(context.getAccessorIndex().lookup("com.example.Payload", "setEvent")).isPresent();
    }

    @Test
    void resolveMethodReturnConstantShouldUseImplementationIndexedGetter(@TempDir Path tempDir) throws IOException {
        writeJava(tempDir, "com/example/EventType.java", """
                package com.example;
                public interface EventType {
                    String getCode();
                }
                """);
        writeJava(tempDir, "com/example/PayEvent.java", """
                package com.example;
                public class PayEvent implements EventType {
                    private String code = "PAY";
                    public String getCode() { return code; }
                }
                """);

        context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);
        constantExtractor = new ConstantExtractor(context, context.getConstantResolver(), mi -> null);
        constantExtractor.setConstructorAnalyzer(new ConstructorAnalyzer(context, context.getConstantResolver()));

        List<String> constants = constantExtractor.resolveMethodReturnConstant(
                "com.example.EventType", "getCode", 0, new HashSet<>(), null);

        assertThat(constants).contains("PAY");
    }

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