package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer;
import click.kamil.springstatemachineexporter.analysis.spring.SpringBeanRegistry;
import click.kamil.springstatemachineexporter.analysis.spring.SpringContextScanner;
import click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
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.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Set;

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

class ConstructorInjectionTypeNarrowerTest {

    @TempDir
    Path tempDir;

    private CodebaseContext context;
    private InjectionPointAnalyzer injectionAnalyzer;
    private ConstructorAnalyzer constructorAnalyzer;

    @BeforeEach
    void setUp() throws Exception {
        Files.writeString(tempDir.resolve("SuperInjection.java"), """
                package com.example;

                import org.springframework.stereotype.Service;

                interface CollaboratorService {
                    void processEvent(String event);
                }

                @Service
                class MosService implements CollaboratorService {
                    void processEvent(String event) {}
                }

                abstract class BaseStateMachineService<T extends CollaboratorService> {
                    protected final T service;

                    BaseStateMachineService(T service) {
                        this.service = service;
                    }

                    void handle(String event) {
                        service.processEvent(event);
                    }
                }

                @Service
                class MosStateMachineService extends BaseStateMachineService<MosService> {
                    MosStateMachineService(MosService mosService) {
                        super(mosService);
                    }
                }
                """);

        context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setClasspath(ToolingClasspath.currentJvmJarEntries());
        context.scan(Set.of(tempDir), Set.of());

        SpringBeanRegistry registry = new SpringBeanRegistry();
        for (var cu : context.getCompilationUnits()) {
            cu.accept(new SpringContextScanner(registry));
        }
        injectionAnalyzer = new InjectionPointAnalyzer(new SpringDependencyResolver(registry));
        constructorAnalyzer = new ConstructorAnalyzer(context, null);
    }

    @Test
    void shouldIndexSubclassOfGenericBase() {
        List<String> impls = context.getImplementations("com.example.BaseStateMachineService");
        assertThat(impls).contains("com.example.MosStateMachineService");
    }

    @Test
    void shouldMapSuperConstructorParameterIndexForInheritedField() {
        TypeDeclaration subclass = context.getTypeDeclaration("com.example.MosStateMachineService");
        TypeDeclaration base = context.getTypeDeclaration("com.example.BaseStateMachineService");
        assertThat(context.getSuperclassFqn(subclass)).isEqualTo("com.example.BaseStateMachineService");

        MethodDeclaration baseCtor = null;
        for (MethodDeclaration md : base.getMethods()) {
            if (md.isConstructor()) {
                baseCtor = md;
                break;
            }
        }
        assertThat(baseCtor).isNotNull();
        assertThat(constructorAnalyzer.findAssignedParameterIndexForTest(baseCtor, "service")).isEqualTo(0);

        MethodDeclaration ctor = null;
        for (MethodDeclaration md : subclass.getMethods()) {
            if (md.isConstructor()) {
                ctor = md;
                break;
            }
        }
        assertThat(ctor).isNotNull();
        int idx = constructorAnalyzer.findAssignedParameterIndexForTest(ctor, "service");
        assertThat(idx).isEqualTo(0);
    }

    @Test
    void shouldMapInheritedFieldToSubclassConstructorParameter() {
        TypeDeclaration subclass = context.getTypeDeclaration("com.example.MosStateMachineService");
        String narrowed = constructorAnalyzer.resolveSubclassConstructorParameterFqnForInheritedField(
                subclass, "service", injectionAnalyzer);
        assertThat(narrowed).isEqualTo("com.example.MosService");
    }

    @Test
    void shouldNarrowFieldBindingInBaseMethod() {
        TypeDeclaration base = context.getTypeDeclaration("com.example.BaseStateMachineService");
        MethodDeclaration handle = context.findMethodDeclaration(base, "handle", false);
        assertThat(handle).isNotNull();

        final IVariableBinding[] fieldBinding = {null};
        handle.getBody().accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation node) {
                if ("processEvent".equals(node.getName().getIdentifier()) && node.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) {
                    if (sn.resolveBinding() instanceof IVariableBinding vb) {
                        fieldBinding[0] = vb;
                    }
                }
                return super.visit(node);
            }
        });

        assertThat(fieldBinding[0]).isNotNull();
        String narrowed = ConstructorInjectionTypeNarrower.resolveConcreteReceiverTypeFqn(
                fieldBinding[0],
                base,
                context,
                injectionAnalyzer,
                constructorAnalyzer);
        assertThat(narrowed).isEqualTo("com.example.MosService");
    }
}
