package click.kamil.springstatemachineexporter.analysis.resolver;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Stream;

/**
 * Discovers generated {@code .java} sources under Maven {@code target/} and Gradle {@code build/} when
 * they already exist in the workspace. Does not run Maven or Gradle.
 */
@Slf4j
public final class GeneratedSourceDiscovery {

    private static final List<String> BUILD_ROOTS = List.of("target", "build");

    private static final List<String> EXCLUDED_RELATIVE_SEGMENTS = List.of(
            "classes",
            "test-classes",
            "generated-test-sources",
            "generated-test",
            "test",
            "testfixtures",
            "test-fixtures",
            "androidtest",
            "inttest",
            "functionaltest",
            "e2etest",
            "libs",
            "lib",
            "tmp",
            "reports",
            "test-results",
            "surefire",
            "failsafe",
            "surefire-reports",
            "failsafe-reports",
            "cache",
            "caches",
            "outputs",
            "intermediates",
            "kotlin",
            "native",
            "resources",
            "distributions",
            "docs",
            "javadoc",
            "testOutput");

    public Set<Path> discoverSourceRoots(Collection<Path> moduleDirectories) {
        Set<Path> roots = new LinkedHashSet<>();
        if (moduleDirectories == null) {
            return roots;
        }
        for (Path moduleDir : moduleDirectories) {
            if (moduleDir == null || !Files.isDirectory(moduleDir)) {
                continue;
            }
            roots.addAll(discoverSourceRoots(moduleDir));
        }
        return roots;
    }

    public Set<Path> discoverSourceRoots(Path moduleDirectory) {
        Set<Path> roots = new LinkedHashSet<>();
        Path normalizedModule = moduleDirectory.toAbsolutePath().normalize();
        for (String buildRootName : BUILD_ROOTS) {
            Path buildRoot = normalizedModule.resolve(buildRootName);
            if (containsGeneratedJava(normalizedModule, buildRoot)) {
                roots.add(buildRoot);
            }
        }
        if (!roots.isEmpty()) {
            log.info("Discovered generated source roots under {}: {}", normalizedModule, roots);
        }
        return roots;
    }

    public boolean hasGeneratedSources(Path moduleDirectory) {
        return !discoverSourceRoots(moduleDirectory).isEmpty();
    }

    private boolean containsGeneratedJava(Path moduleDirectory, Path buildRoot) {
        if (!Files.isDirectory(buildRoot)) {
            return false;
        }
        try (Stream<Path> files = Files.walk(buildRoot)) {
            return files.filter(Files::isRegularFile)
                    .anyMatch(path -> path.getFileName().toString().endsWith(".java")
                            && !isExcludedBuildPath(moduleDirectory, path));
        } catch (IOException e) {
            log.debug("Failed to scan generated sources under {}", buildRoot, e);
            return false;
        }
    }

    static boolean isExcludedBuildPath(Path moduleDirectory, Path candidate) {
        Path normalizedModule = moduleDirectory.toAbsolutePath().normalize();
        Path normalizedCandidate = candidate.toAbsolutePath().normalize();
        if (!normalizedCandidate.startsWith(normalizedModule)) {
            return true;
        }
        Path relative = normalizedModule.relativize(normalizedCandidate);
        if (relative.getNameCount() < 2) {
            return false;
        }
        String buildRoot = relative.getName(0).toString().toLowerCase(Locale.ROOT);
        if (!buildRoot.equals("target") && !buildRoot.equals("build")) {
            return true;
        }
        for (int i = 1; i < relative.getNameCount(); i++) {
            String segment = relative.getName(i).toString().toLowerCase(Locale.ROOT);
            if (isExcludedBuildSegment(segment)) {
                return true;
            }
        }
        return false;
    }

    static boolean isExcludedBuildSegment(String segment) {
        if (segment == null || segment.isBlank()) {
            return false;
        }
        String lower = segment.toLowerCase(Locale.ROOT);
        if (EXCLUDED_RELATIVE_SEGMENTS.contains(lower)) {
            return true;
        }
        return lower.contains("test-sources")
                || lower.startsWith("test-")
                || lower.endsWith("-test")
                || lower.contains("-test-");
    }
}
