package click.kamil.springstatemachineexporter.ast.common;

import org.eclipse.jdt.core.dom.TypeDeclaration;
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 CodebaseContextTest {

    @TempDir
    Path tempDir;

    @Test
    void shouldFindEntryPointByAnnotation() throws IOException {
        String source = """
                package com.example;
                import org.springframework.statemachine.config.EnableStateMachine;
                @EnableStateMachine
                public class MyConfig {}
                """;
        Files.writeString(tempDir.resolve("MyConfig.java"), source);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        List<TypeDeclaration> entryPoints = context.findEntryPointClasses(List.of("EnableStateMachine"));
        assertThat(entryPoints).hasSize(1);
        assertThat(entryPoints.get(0).getName().getIdentifier()).isEqualTo("MyConfig");
    }

    @Test
    void shouldFindEntryPointByInheritance() throws IOException {
        String baseSource = """
                package com.example;
                import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
                public abstract class BaseConfig extends StateMachineConfigurerAdapter {}
                """;
        String leafSource = """
                package com.example;
                public class LeafConfig extends BaseConfig {}
                """;
        Files.writeString(tempDir.resolve("BaseConfig.java"), baseSource);
        Files.writeString(tempDir.resolve("LeafConfig.java"), leafSource);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        List<TypeDeclaration> entryPoints = context.findEntryPointClasses(List.of("EnableStateMachine"));
        assertThat(entryPoints).hasSize(1);
        assertThat(entryPoints.get(0).getName().getIdentifier()).isEqualTo("LeafConfig");
    }

    @Test
    void shouldNotFindAbstractClassAsEntryPoint() throws IOException {
        String source = """
                package com.example;
                import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
                public abstract class MyConfig extends StateMachineConfigurerAdapter {}
                """;
        Files.writeString(tempDir.resolve("MyConfig.java"), source);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        List<TypeDeclaration> entryPoints = context.findEntryPointClasses(List.of("EnableStateMachine"));
        assertThat(entryPoints).isEmpty();
    }

    @Test
    void shouldResolveInheritanceWithImports() throws IOException {
        Files.createDirectories(tempDir.resolve("base"));
        Files.createDirectories(tempDir.resolve("leaf"));

        String baseSource = """
                package com.example.base;
                import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
                public abstract class BaseConfig extends StateMachineConfigurerAdapter {}
                """;
        String leafSource = """
                package com.example.leaf;
                import com.example.base.BaseConfig;
                public class LeafConfig extends BaseConfig {}
                """;
        Files.writeString(tempDir.resolve("base/BaseConfig.java"), baseSource);
        Files.writeString(tempDir.resolve("leaf/LeafConfig.java"), leafSource);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        List<TypeDeclaration> entryPoints = context.findEntryPointClasses(List.of("EnableStateMachine"));
        assertThat(entryPoints).hasSize(1);
        assertThat(entryPoints.get(0).getName().getIdentifier()).isEqualTo("LeafConfig");
    }

    @Test
    void shouldFindBeanMethodsReturningStateMachine() throws IOException {
        String source = """
                package com.example;
                import org.springframework.context.annotation.Bean;
                import org.springframework.statemachine.StateMachine;
                public class MyApp {
                    @Bean
                    public StateMachine<String, String> stateMachine() {
                        return null;
                    }
                
                    public StateMachine<String, String> notABean() {
                        return null;
                    }
                }
                """;
        Files.writeString(tempDir.resolve("MyApp.java"), source);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        var beanMethods = context.findBeanMethodsReturning(java.util.Set.of("StateMachine"));
        assertThat(beanMethods).hasSize(1);
        assertThat(beanMethods.get(0).getName().getIdentifier()).isEqualTo("stateMachine");
    }

    @Test
    void shouldFindBeanMethodsReturningStateMachineWithQualifiedName() throws IOException {
        String source = """
                package com.example;
                import org.springframework.context.annotation.Bean;
                public class MyApp {
                    @Bean
                    public org.springframework.statemachine.StateMachine stateMachine() {
                        return null;
                    }
                }
                """;
        Files.writeString(tempDir.resolve("MyApp.java"), source);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        var beanMethods = context.findBeanMethodsReturning(java.util.Set.of("StateMachine"));
        assertThat(beanMethods).hasSize(1);
        assertThat(beanMethods.get(0).getName().getIdentifier()).isEqualTo("stateMachine");
    }

    @Test
    void shouldFindClassWithMultipleAnnotations() throws IOException {
        String source = """
                package com.example;
                @org.springframework.context.annotation.Configuration
                @click.kamil.MyAnnotation
                public class MultiAnnotated {}
                """;
        Files.writeString(tempDir.resolve("MultiAnnotated.java"), source);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        assertThat(context.findEntryPointClasses(List.of("Configuration"))).hasSize(1);
        assertThat(context.findEntryPointClasses(List.of("MyAnnotation"))).hasSize(1);
    }

    @Test
    void shouldIgnoreInterfacesWithAnnotations() throws IOException {
        String source = """
                package com.example;
                import org.springframework.context.annotation.Configuration;
                @Configuration
                public interface MyInterface {}
                """;
        Files.writeString(tempDir.resolve("MyInterface.java"), source);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        assertThat(context.findEntryPointClasses(List.of("Configuration"))).isEmpty();
    }

    @Test
    void shouldFindBeanMethodsWithGenericParameters() throws IOException {
        String source = """
                package com.example;
                import org.springframework.context.annotation.Bean;
                import org.springframework.statemachine.StateMachine;
                public class MyApp {
                    @Bean
                    public StateMachine<States, Events> stateMachine() {
                        return null;
                    }
                }
                """;
        Files.writeString(tempDir.resolve("MyApp.java"), source);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        var beanMethods = context.findBeanMethodsReturning(java.util.Set.of("StateMachine"));
        assertThat(beanMethods).hasSize(1);
    }

    @Test
    void shouldHandleFilesWithOnlyComments() throws IOException {
        String source = """
                // This is just a comment
                /* And another one */
                """;
        Files.writeString(tempDir.resolve("Comments.java"), source);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        assertThat(context.findEntryPointClasses(List.of("EnableStateMachine"))).isEmpty();
    }

    @Test
    void shouldBeCaseSensitiveForAnnotations() throws IOException {
        String source = """
                package com.example;
                @enablestatemachine
                public class WrongCase {}
                """;
        Files.writeString(tempDir.resolve("WrongCase.java"), source);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        assertThat(context.findEntryPointClasses(List.of("EnableStateMachine"))).isEmpty();
    }

    @Test
    void shouldFindClassWithFullyQualifiedAnnotation() throws IOException {
        String source = """
                package com.example;
                @org.springframework.statemachine.config.EnableStateMachine
                public class FqnAnnotated {}
                """;
        Files.writeString(tempDir.resolve("FqnAnnotated.java"), source);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        assertThat(context.findEntryPointClasses(List.of("EnableStateMachine"))).hasSize(1);
    }
}
