package solutions.tretter.githugandroid

import java.io.File

internal class GitEditorWorkflow(
    private val requireNativeGit: () -> File,
    private val prepareLevel: (Level) -> RepoState,
    private val sandboxDir: (Level) -> File,
    private val inspectSandbox: (Level, String) -> RepoState,
    private val runGit: (File, File, List<String>, Map<String, String>) -> ProcessExecutionResult,
    private val shellExecutable: () -> String,
) {
    fun initialContent(level: Level, currentRepo: RepoState, invocation: GitEditorInvocation): String {
        if (invocation.kind == GitEditorCommandKind.PATCH_HUNK) return invocation.initialContent

        val nativeGit = requireNativeGit()
        val (sandboxRoot, workingDir) = repositoryPaths(level, currentRepo)
        val gitDir = File(sandboxRoot, ".git").apply { mkdirs() }
        val capturedFile = File(gitDir, "GITHUG_ANDROID_CAPTURED_EDITOR")
        val editorScript = File(gitDir, "githug-android-capture-editor.sh")
        capturedFile.delete()

        editorScript.writeText(
            """
            |#!/bin/sh
            |cat "$1" > ${capturedFile.absolutePath.toShellSingleQuoted()}
            |exit 1
            |""".trimMargin(),
        )

        val editorEnvironmentName = when (invocation.kind) {
            GitEditorCommandKind.REBASE_TODO -> "GIT_SEQUENCE_EDITOR"
            GitEditorCommandKind.COMMIT_MESSAGE,
            GitEditorCommandKind.TAG_MESSAGE -> "GIT_EDITOR"
            GitEditorCommandKind.PATCH_HUNK -> return invocation.initialContent
        }

        runGit(
            nativeGit,
            workingDir,
            GitSandboxEngine.tokenizeCommand(invocation.command).drop(1),
            mapOf(editorEnvironmentName to editorCommand(editorScript)),
        )

        if (invocation.kind == GitEditorCommandKind.REBASE_TODO && rebaseStateExists(gitDir)) {
            runGit(nativeGit, workingDir, listOf("rebase", "--abort"), emptyMap())
        }

        val content = capturedFile.takeIf { it.isFile }?.readText() ?: invocation.initialContent
        capturedFile.delete()
        editorScript.delete()
        return content
    }

    fun execute(
        level: Level,
        currentRepo: RepoState,
        invocation: GitEditorInvocation,
        message: String,
    ): Pair<RepoState, List<String>> {
        val nativeGit = requireNativeGit()
        val (sandboxRoot, workingDir) = repositoryPaths(level, currentRepo)
        return when (invocation.kind) {
            GitEditorCommandKind.REBASE_TODO -> executeInteractiveRebase(
                level = level,
                currentRepo = currentRepo,
                nativeGit = nativeGit,
                sandboxRoot = sandboxRoot,
                workingDir = workingDir,
                invocation = invocation,
                todo = message,
            )

            GitEditorCommandKind.COMMIT_MESSAGE,
            GitEditorCommandKind.TAG_MESSAGE -> executeMessageCommand(
                level = level,
                currentRepo = currentRepo,
                nativeGit = nativeGit,
                sandboxRoot = sandboxRoot,
                workingDir = workingDir,
                invocation = invocation,
                message = message,
            )

            GitEditorCommandKind.PATCH_HUNK -> {
                currentRepo to listOf("Patch hunk editing is handled by Git, not the Android runtime.")
            }
        }
    }

    private fun executeMessageCommand(
        level: Level,
        currentRepo: RepoState,
        nativeGit: File,
        sandboxRoot: File,
        workingDir: File,
        invocation: GitEditorInvocation,
        message: String,
    ): Pair<RepoState, List<String>> {
        val messageFile = File(sandboxRoot, ".git/GITHUG_ANDROID_EDITMSG").apply {
            parentFile?.mkdirs()
            writeText(message)
        }
        val arguments = GitSandboxEngine.tokenizeCommand(invocation.command)
            .drop(1)
            .toMutableList()
            .apply { addAll(listOf("-F", messageFile.absolutePath)) }
        val result = runGit(nativeGit, workingDir, arguments, emptyMap())
        messageFile.delete()

        return inspectSandbox(level, currentRepo.currentDir).copy(currentDir = currentRepo.currentDir) to result.outputLines
    }

    private fun executeInteractiveRebase(
        level: Level,
        currentRepo: RepoState,
        nativeGit: File,
        sandboxRoot: File,
        workingDir: File,
        invocation: GitEditorInvocation,
        todo: String,
    ): Pair<RepoState, List<String>> {
        val gitDir = File(sandboxRoot, ".git").apply { mkdirs() }
        val todoFile = File(gitDir, "GITHUG_ANDROID_REBASE_TODO").apply {
            writeText(todo)
        }
        val editorScript = File(gitDir, "githug-android-sequence-editor.sh").apply {
            writeText(
                """
                |#!/bin/sh
                |cat ${todoFile.absolutePath.toShellSingleQuoted()} > "$1"
                |""".trimMargin(),
            )
        }
        val result = runGit(
            nativeGit,
            workingDir,
            GitSandboxEngine.tokenizeCommand(invocation.command).drop(1),
            mapOf(
                "GIT_SEQUENCE_EDITOR" to editorCommand(editorScript),
                "GIT_EDITOR" to "true",
            ),
        )
        todoFile.delete()
        editorScript.delete()

        return inspectSandbox(level, currentRepo.currentDir).copy(currentDir = currentRepo.currentDir) to result.outputLines
    }

    private fun repositoryPaths(level: Level, currentRepo: RepoState): Pair<File, File> {
        val sandboxRoot = sandboxDir(level).canonicalFile
        if (!sandboxRoot.exists()) {
            prepareLevel(level)
        }
        val workingDir = File(sandboxRoot, currentRepo.currentDir).canonicalFile
            .takeIf { it.path == sandboxRoot.path || it.path.startsWith(sandboxRoot.path + File.separator) }
            ?: sandboxRoot
        return sandboxRoot to workingDir
    }

    private fun editorCommand(script: File): String {
        return "${shellExecutable().toShellSingleQuoted()} ${script.absolutePath.toShellSingleQuoted()}"
    }

    private fun rebaseStateExists(gitDir: File): Boolean {
        return File(gitDir, "rebase-merge").exists() || File(gitDir, "rebase-apply").exists()
    }

    private fun String.toShellSingleQuoted(): String {
        return "'" + replace("'", "'\"'\"'") + "'"
    }
}
