GradleIntegration.kt

package com.depanalyzer.parser.gradle

import com.depanalyzer.cli.ProgressTracker
import com.depanalyzer.core.graph.DependencyNode
import com.depanalyzer.parser.GradleGroovyDependencyParser
import com.depanalyzer.parser.GradleKotlinDependencyParser
import java.io.File
import kotlin.time.Duration.Companion.seconds

object GradleIntegration {

    fun analyzeGradleProject(
        projectDir: File,
        enableGradle: Boolean = true,
        verbose: Boolean = false,
        timeoutSeconds: Long = 1800L,
        showCommandOutput: Boolean = false
    ): List<DependencyNode> {
        require(projectDir.exists() && projectDir.isDirectory) { "Project directory must exist: ${projectDir.absolutePath}" }

        if (!enableGradle) {
            ProgressTracker.logWarning("Análisis dinámico deshabilitado. Usando análisis estático (menos preciso).")
            if (verbose) {
                System.err.println("[GradleIntegration] Dynamic Gradle analysis disabled, using static parsing")
            }
            return fallbackToStaticParsing(projectDir, verbose)
        }

        ProgressTracker.logSearching("Buscando Gradle...")
        val gradleCommand = GradleDetector.findGradleCommand(projectDir, verbose)
        if (gradleCommand == null) {
            ProgressTracker.logWarning("Gradle no encontrado. Usando análisis estático (menos preciso).")
            if (verbose) {
                System.err.println("[GradleIntegration] No gradle command found (checked: project wrapper and global gradle), falling back to static parsing")
            }
            return fallbackToStaticParsing(projectDir, verbose)
        }

        if (shouldSkipDynamicForNestedBuild(projectDir)) {
            ProgressTracker.logWarning(
                "Análisis dinámico no compatible en subproyecto Gradle sin settings propio. Usando análisis estático."
            )
            if (verbose) {
                System.err.println("[GradleIntegration] Nested build mismatch risk detected (global gradle + parent settings). Falling back to static parsing")
            }
            return fallbackToStaticParsing(projectDir, verbose)
        }

        ProgressTracker.logProcessing("Analizando dependencias Gradle...")
        return try {
            if (verbose) {
                System.err.println("[GradleIntegration] Starting dynamic Gradle analysis")
            }

            val output = GradleCommandExecutor.execute(
                projectDir,
                timeout = timeoutSeconds.seconds,
                verbose = verbose,
                isDefaultTimeout = (timeoutSeconds == 1800L),
                onOutputLine = if (showCommandOutput) {
                    { line -> ProgressTracker.logStep("   [gradle] $line") }
                } else {
                    null
                }
            )
                ?: run {
                    val errorInfo = GradleCommandExecutor.getLastErrorInfo()
                    val errorReason = errorInfo?.let { " (${it.message})" } ?: ""
                    ProgressTracker.logWarning("Análisis dinámico falló$errorReason. Usando análisis estático (menos preciso).")
                    if (verbose) {
                        System.err.println("[GradleIntegration] Gradle command returned null, falling back to static parsing")
                        if (errorInfo != null) {
                            System.err.println("[GradleIntegration] Error type: ${errorInfo.type}")
                            System.err.println("[GradleIntegration] Suggested flags: ${errorInfo.suggestedFlags}")
                        }
                    }
                    return fallbackToStaticParsing(projectDir, verbose)
                }

            val nodes = GradleDependencyTreeParser.parse(output, verbose)
            if (nodes.isEmpty()) {
                ProgressTracker.logWarning("Análisis dinámico falló. Usando análisis estático (menos preciso).")
                if (verbose) {
                    System.err.println("[GradleIntegration] Gradle output parsing produced no nodes, falling back to static parsing")
                }
                fallbackToStaticParsing(projectDir, verbose)
            } else {
                if (verbose) {
                    System.err.println("[GradleIntegration] Successfully parsed ${nodes.size} root dependencies from gradle")
                }
                ProgressTracker.logSuccess("${nodes.size} dependencias encontradas")
                nodes
            }
        } catch (e: Exception) {
            ProgressTracker.logWarning("Análisis dinámico falló. Usando análisis estático (menos preciso).")
            if (verbose) {
                System.err.println("[GradleIntegration] Exception during Gradle analysis, falling back to static parsing")
                e.printStackTrace(System.err)
            }
            fallbackToStaticParsing(projectDir, verbose)
        }
    }

    private fun fallbackToStaticParsing(projectDir: File, verbose: Boolean = false): List<DependencyNode> {
        if (verbose) {
            System.err.println("[GradleIntegration] Using static parsing fallback")
        }

        val buildFileKts = File(projectDir, "build.gradle.kts")
        val buildFileGroovy = File(projectDir, "build.gradle")

        val buildFile = when {
            buildFileKts.exists() -> buildFileKts
            buildFileGroovy.exists() -> buildFileGroovy
            else -> {
                if (verbose) {
                    System.err.println("[GradleIntegration] No build.gradle or build.gradle.kts found")
                }
                return emptyList()
            }
        }

        val parsedDeps = try {
            when {
                buildFile.name == "build.gradle.kts" -> {
                    if (verbose) {
                        System.err.println("[GradleIntegration] Using Kotlin DSL parser")
                    }
                    GradleKotlinDependencyParser().parse(buildFile)
                }

                else -> {
                    if (verbose) {
                        System.err.println("[GradleIntegration] Using Groovy DSL parser")
                    }
                    GradleGroovyDependencyParser().parse(buildFile)
                }
            }
        } catch (e: Exception) {
            if (verbose) {
                System.err.println("[GradleIntegration] Error during static parsing:")
                e.printStackTrace(System.err)
            }
            emptyList()
        }

        val nodes = parsedDeps.filter { it.version != null }.map { dep ->
            DependencyNode(
                id = "${dep.groupId}:${dep.artifactId}:${dep.version}",
                groupId = dep.groupId,
                artifactId = dep.artifactId,
                version = dep.version!!,
                parent = null,
                children = mutableListOf(),
                scope = mapConfigurationToScope(dep.configuration),
                isDependencyManagement = false
            )
        }

        ProgressTracker.logSuccess("${nodes.size} dependencias encontradas (análisis estático)")
        return nodes
    }

    private fun shouldSkipDynamicForNestedBuild(projectDir: File): Boolean {
        val absoluteProjectDir = projectDir.absoluteFile
        val hasLocalSettings = File(absoluteProjectDir, "settings.gradle").exists() ||
                File(absoluteProjectDir, "settings.gradle.kts").exists()
        if (hasLocalSettings) return false

        var parent = absoluteProjectDir.parentFile
        while (parent != null) {
            if (File(parent, "settings.gradle").exists() || File(parent, "settings.gradle.kts").exists()) {
                return true
            }
            parent = parent.parentFile
        }
        return false
    }

    private fun mapConfigurationToScope(configName: String): String {
        return when {
            configName.contains("compile", ignoreCase = true) && !configName.contains("test", ignoreCase = true) ->
                "compile"

            configName.contains("runtime", ignoreCase = true) && !configName.contains("test", ignoreCase = true) ->
                "runtime"

            configName.contains("test", ignoreCase = true) -> "test"
            configName.contains("provided", ignoreCase = true) -> "provided"
            else -> "compile"
        }
    }
}