package com.correx.core.kernel.orchestration

import com.correx.core.tools.process.ChildProcess
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import java.nio.file.Path

/**
 * Process-spawning [StaticAnalysisRunner]: splits each operator-configured command on whitespace
 * into argv (no shell), runs it in the workspace root with stderr merged into stdout, and returns
 * its exit code with the combined output. A spawn failure or timeout is reported as a non-zero exit
 * so the gate treats a hung or unrunnable check as a failure rather than silently passing.
 *
 * Commands come from the operator's workflow config (trusted, like prompt files), not from the LLM,
 * so they bypass the tool-approval pipeline by design. Mirrors the streaming/timeout shape of
 * ShellTool to avoid pipe-buffer deadlock on large output.
 */
class ProcessStaticAnalysisRunner(
    private val timeoutMs: Long = DEFAULT_TIMEOUT_MS,
) : StaticAnalysisRunner {

    override suspend fun run(workingDir: Path, command: String): StaticAnalysisRunResult =
        withContext(Dispatchers.IO) {
            val argv = command.trim().split(WHITESPACE).filter { it.isNotEmpty() }
            if (argv.isEmpty()) return@withContext StaticAnalysisRunResult(EXIT_NOT_RUN, "empty command")
            val process = runCatching {
                ChildProcess.builder(argv, workingDir.toFile()).redirectErrorStream(true).start()
            }.getOrElse {
                return@withContext StaticAnalysisRunResult(EXIT_NOT_RUN, "failed to start '$command': ${it.message}")
            }
            runCatching {
                withTimeout(timeoutMs) {
                    val outputDeferred = async { process.inputStream.bufferedReader().use { it.readText() } }
                    val exit = process.waitFor()
                    StaticAnalysisRunResult(exit, outputDeferred.await())
                }
            }.getOrElse {
                process.destroyForcibly()
                val reason = if (it is TimeoutCancellationException) {
                    "static analysis '$command' timed out after ${timeoutMs}ms"
                } else {
                    it.message ?: "error running '$command'"
                }
                StaticAnalysisRunResult(EXIT_NOT_RUN, reason)
            }
        }

    private companion object {
        const val DEFAULT_TIMEOUT_MS = 300_000L
        const val EXIT_NOT_RUN = -1
        val WHITESPACE = Regex("\\s+")
    }
}
