package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
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.Map;
import java.util.concurrent.ConcurrentHashMap;

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

class ExpressionAccessClassifierTest {

    @TempDir
    Path tempDir;

    private CodebaseContext context;
    private ExpressionAccessClassifier classifier;
    private static final String SCOPE = "com.example.Service.handle";

    @BeforeEach
    void setUp() throws IOException {
        String source = """
                package com.example;

                import java.util.Map;
                import java.util.function.Supplier;

                public class Service {
                    private static final Map<String, String> ROUTES = Map.of("k", "v");
                    private EventWrapper wrapper;
                    private Supplier<String> eventSupplier;

                    public void handle(String commandKey) {
                        ROUTES.get(commandKey);
                        Routes.ROUTES.get(commandKey);
                        wrapper.getEvent();
                        eventSupplier.get();
                    }
                }

                class Routes {
                    static final Map<String, String> ROUTES = Map.of("k", "v");
                }

                class EventWrapper {
                    String getEvent() { return null; }
                }
                """;
        Files.writeString(tempDir.resolve("Service.java"), source);
        context = new CodebaseContext();
        context.scan(tempDir);

        Map<String, ASTNode> cache = new ConcurrentHashMap<>();
        ConstantResolver constantResolver = new ConstantResolver();
        VariableTracer variableTracer = new VariableTracer(context, constantResolver);
        classifier = new ExpressionAccessClassifier(
                context,
                variableTracer,
                constantResolver,
                expr -> cache.computeIfAbsent(expr, ExpressionAccessClassifierTest::parseExpression));
    }

    @Test
    void shouldClassifyMapLookupByDeclaredFieldType() {
        assertThat(classifier.classifyTrailingAccess("ROUTES.get(commandKey)", SCOPE))
                .isEqualTo(ExpressionAccessClassifier.AccessKind.KEYED_LOOKUP);
    }

    @Test
    void shouldClassifyQualifiedStaticMapLookup() {
        assertThat(classifier.classifyTrailingAccess("Routes.ROUTES.get(commandKey)", SCOPE))
                .isEqualTo(ExpressionAccessClassifier.AccessKind.KEYED_LOOKUP);
    }

    @Test
    void shouldClassifyBeanGetterAsTraceable() {
        assertThat(classifier.classifyTrailingAccess("wrapper.getEvent()", SCOPE))
                .isEqualTo(ExpressionAccessClassifier.AccessKind.TRACEABLE_ACCESSOR);
    }

    @Test
    void shouldClassifySupplierGetAsTraceable() {
        assertThat(classifier.classifyTrailingAccess("eventSupplier.get()", SCOPE))
                .isEqualTo(ExpressionAccessClassifier.AccessKind.TRACEABLE_ACCESSOR);
    }

    @Test
    void shouldClassifyGetOrDefaultAsKeyedLookup() {
        assertThat(classifier.classifyTrailingAccess("ROUTES.getOrDefault(commandKey, \"fallback\")", SCOPE))
                .isEqualTo(ExpressionAccessClassifier.AccessKind.KEYED_LOOKUP);
    }

    @Test
    void shouldDetectMapLikeAndListLikeTypes() {
        assertThat(ExpressionAccessClassifier.isMapLikeType("Map<String, String>")).isTrue();
        assertThat(ExpressionAccessClassifier.isMapLikeType("java.util.HashMap")).isTrue();
        assertThat(ExpressionAccessClassifier.isIndexedCollectionType("List<OrderEvent>")).isTrue();
        assertThat(ExpressionAccessClassifier.isMapLikeType("Supplier<String>")).isFalse();
    }

    private static ASTNode parseExpression(String expr) {
        ASTParser parser = ASTParser.newParser(AST.JLS17);
        Map<String, String> options = org.eclipse.jdt.core.JavaCore.getOptions();
        org.eclipse.jdt.core.JavaCore.setComplianceOptions(org.eclipse.jdt.core.JavaCore.VERSION_17, options);
        parser.setCompilerOptions(options);
        parser.setKind(ASTParser.K_EXPRESSION);
        parser.setSource(expr.toCharArray());
        return parser.createAST(null);
    }
}
