package solutions.tretter.githugandroid

import java.io.File

internal const val SyntheticRemoteUrl = "remote"

internal class GitLevelMaterializer(
    private val runGitCommand: (File, File, List<String>, Map<String, String>) -> ProcessExecutionResult,
) {
    fun materialize(nativeGit: File, sandbox: File, desired: RepoState, level: Level) {
        level.nativeSetup?.let { setup ->
            val nativeSetup = NativeLevelSetup(sandbox) { directory, arguments, environment ->
                runGit(nativeGit, directory, arguments, environment).exitCode
            }
            if (nativeSetup.setup()) {
                return
            }
        }

        desired.config.forEach { (key, value) ->
            runGit(nativeGit, sandbox, listOf("config", key, value))
        }

        val trackedFiles = desired.files.filter { it.tracked }
        val desiredCommitCount = maxOf(
            desired.commits.size,
            desired.branches.values.maxOrNull() ?: 0,
            if (desired.tags.isNotEmpty()) 1 else 0,
        )
        if (desiredCommitCount > 0) {
            repeat(desiredCommitCount) { index ->
                filesForSetupCommit(trackedFiles, index, desiredCommitCount).forEach { file ->
                    runGit(nativeGit, sandbox, listOf("add", file.name))
                }
                val message = desired.commits.getOrNull(index)?.message ?: "Setup commit ${index + 1}"
                runGit(nativeGit, sandbox, listOf("commit", "--allow-empty", "-m", message))
            }

            desired.branches
                .keys
                .filterNot { it == desired.headBranch }
                .forEach { branch -> runGit(nativeGit, sandbox, listOf("branch", branch)) }
            if (desired.branches.containsKey(desired.headBranch)) {
                runGit(nativeGit, sandbox, listOf("checkout", desired.headBranch))
            }
        }

        desired.tags.forEach { tag -> runGit(nativeGit, sandbox, listOf("tag", tag)) }
        desired.remotes.forEach { (name, url) ->
            val materializedUrl = if (url == SyntheticRemoteUrl) {
                materializeSyntheticRemote(nativeGit, sandbox, name)
            } else {
                url
            }
            runGit(nativeGit, sandbox, listOf("remote", "add", name, materializedUrl))
            if (url == SyntheticRemoteUrl) {
                runGit(nativeGit, sandbox, listOf("fetch", name))
                runGit(nativeGit, sandbox, listOf("branch", "--set-upstream-to=$name/master", desired.headBranch))
            }
        }

        val stagedTargets = desired.files.filter { it.staged }.map { it.name }
        if (stagedTargets.isNotEmpty()) {
            runGit(nativeGit, sandbox, listOf("add") + stagedTargets)
        }
        desired.files.filter { it.deleted }.forEach { file ->
            File(sandbox, file.name).delete()
        }
    }

    private fun filesForSetupCommit(files: List<GitFile>, index: Int, commitCount: Int): List<GitFile> {
        if (files.isEmpty()) return emptyList()
        if (commitCount <= 1) return files
        if (index == 0) return files.take(1)
        if (index == commitCount - 1) return files.drop(index)
        return files.getOrNull(index)?.let { listOf(it) }.orEmpty()
    }

    private fun materializeSyntheticRemote(nativeGit: File, sandbox: File, remoteName: String): String {
        val remoteDir = File(sandbox.parentFile, "${sandbox.name}-$remoteName.git")
        remoteDir.deleteRecursively()
        val cloneResult = runGit(nativeGit, sandbox.parentFile ?: sandbox, listOf("clone", "--bare", sandbox.absolutePath, remoteDir.absolutePath))
        if (cloneResult.exitCode != 0) {
            remoteDir.mkdirs()
            runGit(nativeGit, remoteDir, listOf("init", "--bare"))
        }
        return remoteDir.absolutePath
    }

    private fun runGit(
        binary: File,
        workingDir: File,
        arguments: List<String>,
        environment: Map<String, String> = emptyMap(),
    ): ProcessExecutionResult = runGitCommand(binary, workingDir, arguments, environment)
}
