package click.kamil.springstatemachineexporter.analysis.service;

import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Slf4j
public class DynamicClasspathResolver {

    public List<String> resolveClasspath(Path projectRoot) {
        log.info("Attempting dynamic classpath resolution for project at {}", projectRoot);
        if (projectRoot == null || !Files.exists(projectRoot)) {
            return Collections.emptyList();
        }

        if (Files.exists(projectRoot.resolve("pom.xml"))) {
            return resolveMaven(projectRoot);
        } else if (Files.exists(projectRoot.resolve("build.gradle")) || Files.exists(projectRoot.resolve("build.gradle.kts"))) {
            return resolveGradle(projectRoot);
        }

        log.info("No Maven or Gradle configuration found at {}. Proceeding without dynamic classpath.", projectRoot);
        return Collections.emptyList();
    }

    protected List<String> resolveMaven(Path projectRoot) {
        log.info("Maven project detected. Resolving dependencies...");
        try {
            String mvnCmd = getCommand(projectRoot, "mvnw", "mvn");
            if (mvnCmd == null) {
                log.warn("Maven executable not found.");
                return Collections.emptyList();
            }

            ProcessBuilder pb = new ProcessBuilder(
                    mvnCmd,
                    "-q",
                    "dependency:build-classpath",
                    "-DincludeScope=compile",
                    "-Dmdep.outputFile=sme_cp.txt"
            );
            pb.directory(projectRoot.toFile());
            
            runProcess(pb);

            java.util.Set<String> allPaths = new java.util.LinkedHashSet<>();
            try (java.util.stream.Stream<Path> stream = Files.walk(projectRoot)) {
                stream.filter(p -> p.getFileName().toString().equals("sme_cp.txt"))
                      .forEach(cpFile -> {
                          try {
                              String cpString = Files.readString(cpFile).trim();
                              if (!cpString.isEmpty()) {
                                  allPaths.addAll(Arrays.asList(cpString.split(File.pathSeparator)));
                              }
                              Files.deleteIfExists(cpFile);
                          } catch (IOException e) {
                              log.warn("Failed to read or delete sme_cp.txt at {}", cpFile, e);
                          }
                      });
            }
            if (!allPaths.isEmpty()) {
                return new ArrayList<>(allPaths);
            }
        } catch (Exception e) {
            log.error("Failed to dynamically resolve Maven classpath", e);
        }
        return Collections.emptyList();
    }

    protected List<String> resolveGradle(Path projectRoot) {
        log.info("Gradle project detected. Resolving dependencies...");
        Path initScript = null;
        try {
            initScript = Files.createTempFile("state_machine_exporter_init", ".gradle");
            String gradleCmd = getCommand(projectRoot, "gradlew", "gradle");
            if (gradleCmd == null) {
                log.warn("Gradle executable not found.");
                return Collections.emptyList();
            }

            String scriptContent = """
                    allprojects {
                        task smePrintClasspath {
                            doLast {
                                def cp = []
                                if (configurations.findByName("compileClasspath")) {
                                    cp = configurations.compileClasspath.files
                                }
                                println "SME_CLASSPATH_MARKER:" + cp.join(File.pathSeparator)
                            }
                        }
                    }
                    """;
            Files.writeString(initScript, scriptContent);

            ProcessBuilder pb = new ProcessBuilder(
                    gradleCmd,
                    "-q",
                    "-I", initScript.toAbsolutePath().toString(),
                    "smePrintClasspath"
            );
            pb.directory(projectRoot.toFile());

            String output = runProcessAndGetOutput(pb);
            java.util.Set<String> allPaths = new java.util.LinkedHashSet<>();
            for (String line : output.split("\\r?\\n")) {
                if (line.startsWith("SME_CLASSPATH_MARKER:")) {
                    String cpString = line.substring("SME_CLASSPATH_MARKER:".length()).trim();
                    if (!cpString.isEmpty()) {
                        allPaths.addAll(Arrays.asList(cpString.split(File.pathSeparator)));
                    }
                }
            }
            if (!allPaths.isEmpty()) {
                return new ArrayList<>(allPaths);
            }
        } catch (Exception e) {
            log.error("Failed to dynamically resolve Gradle classpath", e);
        } finally {
            if (initScript != null) {
                try {
                    Files.deleteIfExists(initScript);
                } catch (IOException ignored) {}
            }
        }
        return Collections.emptyList();
    }

    private String getCommand(Path projectRoot, String wrapper, String systemBinary) {
        Path wrapperUnix = projectRoot.resolve(wrapper);
        Path wrapperWin = projectRoot.resolve(wrapper + ".bat");
        
        boolean isWin = System.getProperty("os.name").toLowerCase().contains("win");
        
        if (isWin && Files.exists(wrapperWin)) return wrapperWin.toAbsolutePath().toString();
        if (!isWin && Files.exists(wrapperUnix)) {
            // Need absolute path like ./gradlew
            return wrapperUnix.toAbsolutePath().toString();
        }
        
        return systemBinary;
    }

    protected void runProcess(ProcessBuilder pb) throws IOException, InterruptedException {
        pb.redirectErrorStream(true);
        pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
        Process p = pb.start();
        p.waitFor();
    }

    protected String runProcessAndGetOutput(ProcessBuilder pb) throws IOException, InterruptedException {
        pb.redirectErrorStream(true);
        Process p = pb.start();
        StringBuilder out = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
            String line;
            while ((line = br.readLine()) != null) {
                out.append(line).append("\n");
            }
        }
        p.waitFor();
        return out.toString();
    }
}
