package com.kawomi.githugandroid

import android.os.Build
import android.content.Context
import java.io.File

class GitRepositoryRuntime(private val context: Context) {
    private val sandboxesRoot = File(context.filesDir, "githug-sandboxes")
    private val extractedGitDir = File(context.filesDir, "native-git/bin")

    fun startupBanner(): String {
        return if (nativeGitBinary() != null) {
            "Welcome to GitHug Android. Native Git prototype ready."
        } else {
            "Welcome to GitHug Android. Native Git binary not bundled for this ABI yet; using in-memory fallback."
        }
    }

    fun prepareLevel(level: Level): RepoState {
        val nativeGit = nativeGitBinary()
        if (nativeGit == null) {
            return level.setup()
        }

        val sandbox = sandboxDir(level)
        sandbox.deleteRecursively()
        sandbox.mkdirs()

        val desired = level.setup()
        desired.files.forEach { file ->
            File(sandbox, file.name).apply {
                parentFile?.mkdirs()
                writeText(file.content)
            }
        }

        val needsGit = desired.initialized || desired.files.any { it.staged || it.tracked } || desired.commits.isNotEmpty()
        if (needsGit) {
            val initResult = runGit(nativeGit, sandbox, listOf("init", "-b", desired.headBranch))
            if (initResult.exitCode != 0) {
                runGit(nativeGit, sandbox, listOf("init"))
                runGit(nativeGit, sandbox, listOf("checkout", "-B", desired.headBranch))
            }

            val stageTargets = desired.files.filter { it.staged || it.tracked }.map { it.name }
            if (stageTargets.isNotEmpty()) {
                runGit(nativeGit, sandbox, listOf("add") + stageTargets)
            }
        }

        return inspectSandbox(level)
    }

    fun execute(level: Level, currentRepo: RepoState, command: String): Pair<RepoState, List<String>> {
        val nativeGit = nativeGitBinary()
        if (nativeGit == null) {
            return GitSandboxEngine.execute(currentRepo, command)
        }

        val sandbox = sandboxDir(level)
        if (!sandbox.exists()) {
            prepareLevel(level)
        }

        val tokens = GitSandboxEngine.tokenizeCommand(command)
        if (tokens.isEmpty()) return inspectSandbox(level) to emptyList()

        val output = when (tokens.first()) {
            "git" -> runGit(nativeGit, sandbox, tokens.drop(1)).outputLines
            "help", "?" -> commandReferenceLines()
            else -> executeHelperCommand(sandbox, tokens)
        }

        return inspectSandbox(level) to output
    }

    fun commandReferenceLines(): List<String> {
        return buildList {
            addAll(GitSandboxEngine.commandReferenceLines())
            add("Native Git prototype:")
            add("  binary path: nativeLibraryDir/libgit.so")
            add("  selected ABI: ${Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"}")
            add("  helper commands: ls, pwd, cat, touch, mkdir, rm, echo")
        }
    }

    private fun executeHelperCommand(workingDir: File, tokens: List<String>): List<String> {
        return when (tokens.first()) {
            "ls" -> workingDir.listFiles()
                ?.filterNot { it.name == ".git" }
                ?.sortedBy { it.name }
                ?.map { it.name }
                .orEmpty()
            "pwd" -> listOf("/sandbox/${workingDir.name}")
            "cat" -> {
                val target = tokens.getOrNull(1) ?: return listOf("usage: cat <file>")
                val file = File(workingDir, target)
                if (!file.exists() || file.isDirectory) listOf("cat: $target: No such file")
                else file.readLines().ifEmpty { listOf("") }
            }
            "touch" -> {
                val target = tokens.getOrNull(1) ?: return listOf("usage: touch <file>")
                val file = File(workingDir, target)
                if (file.exists()) {
                    listOf("$target already exists")
                } else {
                    file.parentFile?.mkdirs()
                    file.writeText("")
                    emptyList()
                }
            }
            "mkdir" -> {
                val target = tokens.getOrNull(1) ?: return listOf("usage: mkdir <dir>")
                val dir = File(workingDir, target)
                if (dir.exists()) listOf("mkdir: $target: File exists") else {
                    dir.mkdirs()
                    emptyList()
                }
            }
            "rm" -> {
                val target = tokens.getOrNull(1) ?: return listOf("usage: rm <path>")
                val file = File(workingDir, target)
                if (!file.exists()) listOf("rm: $target: No such file or directory") else {
                    file.deleteRecursively()
                    emptyList()
                }
            }
            "echo" -> listOf(tokens.drop(1).joinToString(" "))
            else -> listOf("Command not supported in prototype runtime. Try a git command or helper command.")
        }
    }

    private fun inspectSandbox(level: Level): RepoState {
        val sandbox = sandboxDir(level)
        val nativeGit = nativeGitBinary()
        val filesOnDisk = sandbox.listFiles()
            ?.filter { it.isFile && it.name != ".git" }
            .orEmpty()

        if (nativeGit == null || !File(sandbox, ".git").exists()) {
            return RepoState(
                initialized = File(sandbox, ".git").exists(),
                files = filesOnDisk.map { GitFile(name = it.name, content = it.readText()) },
            )
        }

        val statusResult = runGit(nativeGit, sandbox, listOf("status", "--porcelain"))
        val statusMap = mutableMapOf<String, Pair<Boolean, Boolean>>()
        statusResult.outputLines.forEach { line ->
            if (line.length < 4) return@forEach
            val x = line[0]
            val y = line[1]
            val path = line.substring(3).trim()
            val staged = x != ' ' && x != '?'
            val tracked = x != '?' || y != '?'
            statusMap[path] = staged to tracked
        }

        val logResult = runGit(nativeGit, sandbox, listOf("log", "--pretty=format:%h\t%s"))
        val branchResult = runGit(nativeGit, sandbox, listOf("branch", "--list"))
        val headResult = runGit(nativeGit, sandbox, listOf("branch", "--show-current"))
        val commits = if (logResult.exitCode == 0) {
            logResult.outputLines.filter { it.isNotBlank() }.mapNotNull { line ->
                val parts = line.split('\t', limit = 2)
                if (parts.isEmpty()) null else CommitNode(parts[0], parts.getOrElse(1) { "" })
            }
        } else {
            emptyList()
        }

        return RepoState(
            initialized = true,
            files = filesOnDisk.map { file ->
                val (staged, tracked) = statusMap[file.name] ?: (false to true)
                GitFile(
                    name = file.name,
                    content = file.readText(),
                    staged = staged,
                    tracked = tracked,
                )
            },
            commits = commits,
            headBranch = headResult.outputLines.firstOrNull()?.ifBlank { null } ?: "master",
            branches = branchResult.outputLines.map { it.removePrefix("*").trim() }.filter { it.isNotBlank() }.associateWith { 0 },
        )
    }

    private fun nativeGitBinary(): File? {
        return packagedNativeGitBinary()
    }

    private fun packagedNativeGitBinary(): File? {
        val nativeLibraryDir = context.applicationInfo.nativeLibraryDir ?: return null
        val candidate = File(nativeLibraryDir, "libgit.so")
        return candidate.takeIf { it.exists() && it.canExecute() }
    }

    private fun sandboxDir(level: Level): File = File(sandboxesRoot, level.id)

    private fun runGit(binary: File, workingDir: File, arguments: List<String>): ProcessExecutionResult {
        return runProcess(binary, workingDir, arguments)
    }

    private fun runProcess(binary: File, workingDir: File, arguments: List<String>): ProcessExecutionResult {
        return try {
            val process = ProcessBuilder(listOf(binary.absolutePath) + arguments)
                .directory(workingDir)
                .redirectErrorStream(true)
                .apply {
                    environment()["HOME"] = workingDir.absolutePath
                    environment()["GIT_CONFIG_NOSYSTEM"] = "1"
                    environment()["GIT_AUTHOR_NAME"] = "GitHug"
                    environment()["GIT_AUTHOR_EMAIL"] = "githug@example.com"
                    environment()["GIT_COMMITTER_NAME"] = "GitHug"
                    environment()["GIT_COMMITTER_EMAIL"] = "githug@example.com"
                    environment()["LC_ALL"] = "C"
                }
                .start()

            val output = process.inputStream.bufferedReader().readLines()
            val exit = process.waitFor()
            ProcessExecutionResult(exitCode = exit, outputLines = output.ifEmpty { if (exit == 0) emptyList() else listOf("Command failed") })
        } catch (error: Exception) {
            ProcessExecutionResult(exitCode = -1, outputLines = listOf("Native Git execution failed: ${error.message ?: error::class.java.simpleName}"))
        }
    }
}

private data class ProcessExecutionResult(
    val exitCode: Int,
    val outputLines: List<String>,
)