package click.kamil.springstatemachineexporter.analysis.resolver;

import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.Nested;
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.Set;

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

class GeneratedSourceDiscoveryTest {

    private final GeneratedSourceDiscovery discovery = new GeneratedSourceDiscovery();

    @Nested
    class MavenTarget {

        @Test
        void shouldDiscoverMapStructLikeSourcesUnderTarget(@TempDir Path tempDir) throws IOException {
            Path module = tempDir.resolve("service");
            Path generated = module.resolve("target/generated-sources/annotations/com/acme/mapper");
            Files.createDirectories(generated);
            Files.writeString(generated.resolve("OrderMapperImpl.java"), """
                    package com.acme.mapper;
                    public class OrderMapperImpl implements OrderMapper {}
                    """);

            assertThat(discovery.discoverSourceRoots(module))
                    .containsExactly(module.resolve("target").normalize());
        }

        @Test
        void shouldIgnoreCompiledClassesUnderTarget(@TempDir Path tempDir) throws IOException {
            Path module = tempDir.resolve("service");
            Path classes = module.resolve("target/classes/com/acme");
            Files.createDirectories(classes);
            Files.writeString(classes.resolve("Accidental.java"), "class Accidental {}");

            assertThat(discovery.discoverSourceRoots(module)).isEmpty();
            assertThat(GeneratedSourceDiscovery.isExcludedBuildPath(module, classes.resolve("Accidental.java")))
                    .isTrue();
        }
    }

    @Nested
    class GradleBuild {

        @Test
        void shouldDiscoverAnnotationProcessorOutput(@TempDir Path tempDir) throws IOException {
            Path module = tempDir.resolve("app");
            Path generated = module.resolve(
                    "build/generated/sources/annotationProcessor/java/main/com/acme/config");
            Files.createDirectories(generated);
            Files.writeString(generated.resolve("ApplicationConfigImpl.java"), """
                    package com.acme.config;
                    public class ApplicationConfigImpl {}
                    """);

            assertThat(discovery.discoverSourceRoots(module))
                    .containsExactly(module.resolve("build").normalize());
        }

        @Test
        void shouldIgnoreGradleBuildClasses(@TempDir Path tempDir) throws IOException {
            Path module = tempDir.resolve("app");
            Path classes = module.resolve("build/classes/java/main/com/acme");
            Files.createDirectories(classes);
            Files.writeString(classes.resolve("ShouldSkip.java"), "class ShouldSkip {}");

            assertThat(discovery.discoverSourceRoots(module)).isEmpty();
        }

        @Test
        void shouldIgnoreGradleTestAnnotationProcessorOutput(@TempDir Path tempDir) throws IOException {
            Path module = tempDir.resolve("app");
            Path generated = module.resolve(
                    "build/generated/sources/annotationProcessor/java/test/com/acme/testsupport");
            Files.createDirectories(generated);
            Files.writeString(generated.resolve("TestSupportImpl.java"), "class TestSupportImpl {}");

            assertThat(discovery.discoverSourceRoots(module)).isEmpty();
        }
    }

    @Nested
    class TestOutputExclusion {

        @Test
        void shouldIgnoreMavenGeneratedTestSources(@TempDir Path tempDir) throws IOException {
            Path module = tempDir.resolve("service");
            Path generated = module.resolve("target/generated-test-sources/test-annotations/com/acme");
            Files.createDirectories(generated);
            Files.writeString(generated.resolve("TestMapperImpl.java"), "class TestMapperImpl {}");

            assertThat(discovery.discoverSourceRoots(module)).isEmpty();
        }

        @Test
        void shouldIgnoreTestClassesUnderTarget(@TempDir Path tempDir) throws IOException {
            Path module = tempDir.resolve("service");
            Path testClasses = module.resolve("target/test-classes/com/acme");
            Files.createDirectories(testClasses);
            Files.writeString(testClasses.resolve("OrderServiceTest.java"), "class OrderServiceTest {}");

            assertThat(discovery.discoverSourceRoots(module)).isEmpty();
        }
    }

    @Nested
    class Integration {

        @Test
        void shouldAddGeneratedRootsToProjectScanPaths(@TempDir Path tempDir) throws IOException {
            Path root = tempDir.resolve("monorepo");
            Files.createDirectories(root.resolve(".git"));
            Files.writeString(root.resolve("settings.gradle"), "include 'api'");
            Path api = root.resolve("api");
            Files.createDirectories(api);
            Files.writeString(api.resolve("build.gradle"), "");
            Path generated = api.resolve("target/generated-sources/annotations/com/example");
            Files.createDirectories(generated);
            Files.writeString(generated.resolve("GeneratedBean.java"), """
                    package com.example;
                    public class GeneratedBean {}
                    """);

            StaticBuildFileAnalyzer analyzer = new StaticBuildFileAnalyzer();
            ProjectModuleGraph graph = analyzer.analyze(root);

            assertThat(graph.resolveScanPaths(root)).contains(api.resolve("target").normalize());
        }

        @Test
        void shouldSkipGeneratedRootsWhenDisabled(@TempDir Path tempDir) throws IOException {
            Path root = tempDir.resolve("monorepo");
            Files.createDirectories(root.resolve(".git"));
            Files.writeString(root.resolve("settings.gradle"), "include 'api'");
            Path api = root.resolve("api");
            Files.createDirectories(api);
            Files.writeString(api.resolve("build.gradle"), "");
            Path generated = api.resolve("target/generated-sources/annotations/com/example");
            Files.createDirectories(generated);
            Files.writeString(generated.resolve("GeneratedBean.java"), "package com.example; class GeneratedBean {}");

            ProjectModuleGraph graph = new StaticBuildFileAnalyzer().analyze(root);

            assertThat(graph.resolveScanPaths(root, false)).doesNotContain(api.resolve("target").normalize());
            assertThat(graph.resolveScanPaths(root, true)).contains(api.resolve("target").normalize());
        }

        @Test
        void shouldIndexGeneratedTypesWhenScanningModule() throws IOException {
            Path module = Files.createTempDirectory("sme-gen-src").resolve("service");
            Path generated = module.resolve("target/generated-sources/annotations/com/acme");
            Files.createDirectories(generated);
            Files.writeString(generated.resolve("GeneratedEvent.java"), """
                    package com.acme;
                    public enum GeneratedEvent { PAY, SHIP }
                    """);

            CodebaseContext context = new CodebaseContext();
            context.scan(Set.of(module), Set.of());

            assertThat(context.getCompilationUnits()).hasSize(1);
            assertThat(context.getEnumValues("GeneratedEvent"))
                    .contains("com.acme.GeneratedEvent.PAY", "com.acme.GeneratedEvent.SHIP");
        }
    }
}
