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.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
public class SiblingDependencyResolver {

    private static final Pattern GRADLE_PROJECT_DEP = Pattern.compile("project\\(['\"]:(.*?)['\"]\\)");
    private static final Pattern MAVEN_DEPENDENCY = Pattern.compile("<dependency>\\s*<groupId>(.*?)</groupId>\\s*<artifactId>(.*?)</artifactId>\\s*<version>(.*?)</version>", Pattern.DOTALL);

    public Set<Path> resolveSiblings(Path projectDir) {
        Set<Path> allSiblings = new HashSet<>();
        Queue<Path> queue = new LinkedList<>();
        queue.add(projectDir);
        
        while (!queue.isEmpty()) {
            Path current = queue.poll();
            Set<Path> currentSiblings = new HashSet<>();
            try {
                currentSiblings.addAll(resolveGradleSiblings(current));
                currentSiblings.addAll(resolveMavenSiblings(current));
            } catch (Exception e) {
                log.warn("Error resolving sibling dependencies for {}", current, e);
            }
            
            for (Path sibling : currentSiblings) {
                if (!allSiblings.contains(sibling) && !sibling.equals(projectDir)) {
                    allSiblings.add(sibling);
                    queue.add(sibling);
                }
            }
        }
        return allSiblings;
    }

    private Set<Path> resolveGradleSiblings(Path projectDir) throws IOException {
        Set<Path> siblings = new HashSet<>();
        Path buildGradle = projectDir.resolve("build.gradle");
        if (!Files.exists(buildGradle)) {
            buildGradle = projectDir.resolve("build.gradle.kts");
        }

        if (Files.exists(buildGradle)) {
            log.info("Found build.gradle at {}", buildGradle);
            String content = Files.readString(buildGradle);
            Matcher matcher = GRADLE_PROJECT_DEP.matcher(content);
            Set<String> gradlePaths = new HashSet<>();
            while (matcher.find()) {
                gradlePaths.add(matcher.group(1));
            }
            log.info("Found project dependencies: {}", gradlePaths);

            if (!gradlePaths.isEmpty()) {
                Path rootDir = findGradleRoot(projectDir);
                log.info("Resolved Gradle root to: {}", rootDir);
                if (rootDir != null) {
                    for (String gradlePath : gradlePaths) {
                        String relativePath = gradlePath.replace(':', '/');
                        Path resolvedSibling = rootDir.resolve(relativePath).normalize();

                        // Check for explicit projectDir mapping in settings.gradle or settings.gradle.kts
                        Path settingsFile = rootDir.resolve("settings.gradle");
                        if (!Files.exists(settingsFile)) {
                            settingsFile = rootDir.resolve("settings.gradle.kts");
                        }
                        if (Files.exists(settingsFile)) {
                            String settingsContent = Files.readString(settingsFile);
                            String regex = "project\\(['\"]:" + gradlePath + "['\"]\\)\\.projectDir\\s*=\\s*(?:file\\(['\"](.*?)['\"]\\)|new File\\(.*?,\\s*['\"](.*?)['\"]\\))";
                            Matcher settingsMatcher = Pattern.compile(regex).matcher(settingsContent);
                            if (settingsMatcher.find()) {
                                String customPath = settingsMatcher.group(1) != null ? settingsMatcher.group(1) : settingsMatcher.group(2);
                                resolvedSibling = rootDir.resolve(customPath).normalize();
                                log.info("Found custom projectDir in settings for {}: {}", gradlePath, resolvedSibling);
                            }
                        }

                        if (Files.exists(resolvedSibling)) {
                            log.info("Discovered local Gradle sibling dependency: {}", resolvedSibling);
                            siblings.add(resolvedSibling);
                        }
                    }
                }
            }
        }
        return siblings;
    }

    private Path findGradleRoot(Path start) {
        Path current = start.toAbsolutePath();
        Path lastSettingsDir = null;
        while (current != null) {
            if (Files.exists(current.resolve("settings.gradle")) || Files.exists(current.resolve("settings.gradle.kts"))) {
                lastSettingsDir = current;
            }
            if (Files.exists(current.resolve(".git"))) {
                return lastSettingsDir != null ? lastSettingsDir : current;
            }
            current = current.getParent();
        }
        return lastSettingsDir;
    }

    private Set<Path> resolveMavenSiblings(Path projectDir) throws IOException {
        Set<Path> siblings = new HashSet<>();
        Path pom = projectDir.resolve("pom.xml");
        if (Files.exists(pom)) {
            log.info("Found pom.xml at {}", pom);
            String content = Files.readString(pom);
            
            Set<String> targetArtifacts = new HashSet<>();
            Matcher matcher = Pattern.compile("<artifactId>(.*?)</artifactId>").matcher(content);
            // We want artifacts from the <dependencies> section
            int depsStart = content.indexOf("<dependencies>");
            int depsEnd = content.indexOf("</dependencies>");
            if (depsStart != -1 && depsEnd > depsStart) {
                String depsContent = content.substring(depsStart, depsEnd);
                Matcher depMatcher = Pattern.compile("<artifactId>(.*?)</artifactId>").matcher(depsContent);
                while (depMatcher.find()) {
                    targetArtifacts.add(depMatcher.group(1));
                }
            }
            log.info("Found maven target artifacts: {}", targetArtifacts);

            if (!targetArtifacts.isEmpty()) {
                Path rootDir = findMavenRoot(projectDir);
                log.info("Resolved Maven root to: {}", rootDir);
                if (rootDir != null) {
                    try (Stream<Path> paths = Files.walk(rootDir)) {
                        List<Path> allPoms = paths.filter(p -> p.getFileName().toString().equals("pom.xml"))
                             .filter(p -> !p.toString().contains("/target/"))
                             .collect(Collectors.toList());
                        log.info("Scanned {} total pom.xml files under {}", allPoms.size(), rootDir);
                        
                         for (Path p : allPoms) {
                             try {
                                 String pContent = Files.readString(p);
                                 String artifactId = extractOwnArtifactId(pContent);
                                 log.info("Checking artifactId {} in {}", artifactId, p);
                                 if (artifactId != null && targetArtifacts.contains(artifactId)) {
                                     Path siblingDir = p.getParent();
                                     if (!siblingDir.equals(projectDir)) {
                                        log.info("Discovered local Maven sibling dependency via artifact match: {}", siblingDir);
                                        siblings.add(siblingDir);
                                     }
                                 }
                             } catch (IOException e) {
                                 // ignore
                             }
                        }
                    }
                }
            }

            // Also directly add aggregator <module> directories declared in this pom
            Matcher moduleMatcher = Pattern.compile("<module>(.*?)</module>").matcher(content);
            while (moduleMatcher.find()) {
                String moduleName = moduleMatcher.group(1).trim();
                Path moduleDir = projectDir.resolve(moduleName).normalize();
                if (Files.exists(moduleDir) && Files.exists(moduleDir.resolve("pom.xml")) && !moduleDir.equals(projectDir)) {
                    log.info("Discovered local Maven sibling dependency via <module>: {}", moduleDir);
                    siblings.add(moduleDir);
                }
            }
            
            // Also add parent dir if it has a pom.xml
            Path parentDir = projectDir.getParent();
            
            // Check for explicit <relativePath> in parent block
            Matcher parentRelativePathMatcher = Pattern.compile("<parent>.*?<relativePath>(.*?)</relativePath>.*?</parent>", Pattern.DOTALL).matcher(content);
            if (parentRelativePathMatcher.find()) {
                String relPathStr = parentRelativePathMatcher.group(1).trim();
                Path relPath = projectDir.resolve(relPathStr).normalize();
                if (Files.isRegularFile(relPath) && relPath.getFileName().toString().equals("pom.xml")) {
                    parentDir = relPath.getParent();
                } else if (Files.isDirectory(relPath)) {
                    parentDir = relPath;
                }
            }
            
            if (parentDir != null && Files.exists(parentDir.resolve("pom.xml")) && !parentDir.equals(projectDir)) {
                log.info("Discovered local Maven parent sibling: {}", parentDir);
                siblings.add(parentDir);
            }
        }
        return siblings;
    }

    private String extractOwnArtifactId(String pomContent) {
        // Strip <parent> block if it exists
        String content = pomContent;
        int parentEnd = pomContent.indexOf("</parent>");
        if (parentEnd != -1) {
            content = pomContent.substring(parentEnd);
        }
        
        Matcher matcher = Pattern.compile("<artifactId>(.*?)</artifactId>").matcher(content);
        if (matcher.find()) {
            return matcher.group(1);
        }
        return null;
    }

    private Path findMavenRoot(Path start) {
        Path current = start.toAbsolutePath();
        Path lastPomDir = null;
        while (current != null) {
            if (Files.exists(current.resolve("pom.xml"))) {
                lastPomDir = current;
            }
            if (Files.exists(current.resolve(".git"))) {
                return lastPomDir != null ? lastPomDir : current;
            }
            current = current.getParent();
        }
        return lastPomDir;
    }
}
