package solutions.tretter.githugandroid

import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.Test
import java.io.File
import java.nio.file.Files

class GitSandboxEngineTest {
    @Test
    fun statusShowsDeletedTrackedFiles() {
        val repo = RepoState(
            initialized = true,
            files = listOf(GitFile("deleteme.rb", tracked = true, deleted = true)),
            commits = listOf(CommitNode("0000001", "Added a temp file")),
            branches = mapOf("master" to 1),
        )

        val (_, output) = GitSandboxEngine.execute(repo, "git status")

        assertTrue(output.any { it.contains("deleted:") && it.contains("deleteme.rb") })
    }

    @Test
    fun gitRmRemovesDeletedTrackedFileFromIndex() {
        val repo = RepoState(
            initialized = true,
            files = listOf(GitFile("deleteme.rb", tracked = true, deleted = true)),
            commits = listOf(CommitNode("0000001", "Added a temp file")),
            branches = mapOf("master" to 1),
        )

        val (updatedRepo, _) = GitSandboxEngine.execute(repo, "git rm deleteme.rb")

        assertFalse(updatedRepo.files.any { it.name == "deleteme.rb" })
    }

    @Test
    fun gitMvExpandsWildcardSourcesIntoDestinationDirectory() {
        val repo = RepoState(
            initialized = true,
            files = listOf(
                GitFile("about.html", tracked = true),
                GitFile("contact.html", tracked = true),
                GitFile("index.html", tracked = true),
                GitFile("README", tracked = true),
            ),
            branches = mapOf("master" to 1),
        )

        val (updatedRepo, _) = GitSandboxEngine.execute(repo, "git mv *.html src")

        assertTrue(updatedRepo.files.any { it.name == "src/about.html" })
        assertTrue(updatedRepo.files.any { it.name == "src/contact.html" })
        assertTrue(updatedRepo.files.any { it.name == "src/index.html" })
        assertTrue(updatedRepo.files.any { it.name == "README" })
    }

    @Test
    fun singleQuotedWildcardIsNotExpanded() {
        val repo = RepoState(
            initialized = true,
            files = listOf(GitFile("README"), GitFile("main.kt")),
        )

        val (_, output) = GitSandboxEngine.execute(repo, "echo '*'")

        assertEquals(listOf("*"), output)
    }

    @Test
    fun echoRedirectAcceptsNoSurroundingSpaces() {
        val repo = RepoState(initialized = true)

        val (updatedRepo, output) = GitSandboxEngine.execute(repo, "echo '*.swp'>.gitignore")

        assertTrue(output.isEmpty())
        assertEquals("*.swp", updatedRepo.files.single { it.name == ".gitignore" }.content)
    }

    @Test
    fun commitHashAnswerAcceptsFullHashThatStartsWithDisplayedShortHash() {
        val repo = RepoState(
            initialized = true,
            commits = listOf(CommitNode("abc1234", "Latest commit")),
            branches = mapOf("master" to 1),
        )

        assertTrue(commitHashAnswer("0000001")(repo, "abc1234fedcba9876543210fedcba9876543210"))
    }

    @Test
    fun mdIsAcceptedAsMkdirAlias() {
        val repo = RepoState(initialized = true)

        val (_, output) = GitSandboxEngine.execute(repo, "md src")

        assertTrue(output.isEmpty())
    }

    @Test
    fun dirIsAcceptedAsLsAlias() {
        val repo = RepoState(
            initialized = true,
            files = listOf(GitFile("README"), GitFile("src/main.kt")),
        )

        val (_, output) = GitSandboxEngine.execute(repo, "dir")

        assertTrue("README" in output)
        assertTrue("src/main.kt" in output)
    }

    @Test
    fun lsShowsDotfiles() {
        val repo = RepoState(
            initialized = true,
            files = listOf(GitFile(".gitignore"), GitFile("README")),
        )

        val (_, output) = GitSandboxEngine.execute(repo, "ls")

        assertTrue(".gitignore" in output)
    }

    @Test
    fun lsShowsGitDirectoryEntry() {
        val repo = RepoState(initialized = true)

        val (_, output) = GitSandboxEngine.execute(repo, "ls")

        assertTrue(".git" in output)
    }

    @Test
    fun fileCompletionUsesCurrentDirectory() {
        val repo = RepoState(
            initialized = true,
            currentDir = "src",
            files = listOf(
                GitFile("README"),
                GitFile("src/main.kt"),
                GitFile("src/model/User.kt"),
            ),
        )

        assertEquals(listOf("main.kt", "model/User.kt"), fileCompletionCandidates(repo).sorted())
        assertEquals(listOf("model/"), directoryCompletionCandidates(repo))
    }

    @Test
    fun nativeCompletionUsesCurrentDirectoryIncludingGitMetadata() {
        val git = File("/usr/bin/git")
        assumeTrue(git.exists() && git.canExecute())
        val root = Files.createTempDirectory("githug-completion").toFile()
        try {
            val runtime = GitRepositoryRuntime(root, git)
            val level = level(
                id = "completion-test",
                title = "Completion Test",
                description = "",
                hints = emptyList(),
                commandSuggestions = emptyList(),
                setup = { RepoState(initialized = true) },
                validator = { _, _ -> false },
            )
            val repo = runtime.prepareLevel(level)
            val (gitDirRepo, _) = runtime.execute(level, repo, "cd .git")

            assertTrue("HEAD" in runtime.completionCandidates(level, gitDirRepo, directoriesOnly = false))
        } finally {
            root.deleteRecursively()
        }
    }

    @Test
    fun nativePushLevelHasDivergedStatusAndRebasesCleanly() {
        val git = File("/usr/bin/git")
        assumeTrue(git.exists() && git.canExecute())
        val root = Files.createTempDirectory("githug-push-level").toFile()
        try {
            val runtime = GitRepositoryRuntime(root, git)
            val level = pushLevel()
            val repo = runtime.prepareLevel(level)

            val (_, statusOutput) = runtime.execute(level, repo, "git status")
            assertTrue(statusOutput.any { it.contains("diverged", ignoreCase = true) })

            val (_, diffOutput) = runtime.execute(level, repo, "git diff")
            assertTrue(diffOutput.isNotEmpty())

            val (rebasedRepo, pullOutput) = runtime.execute(level, repo, "git pull --rebase origin master")
            assertFalse(pullOutput.any { it.contains("fatal:", ignoreCase = true) || it.contains("error:", ignoreCase = true) })
            assertTrue(rebasedRepo.files.any { it.name == "file4" && it.tracked })
        } finally {
            root.deleteRecursively()
        }
    }

    @Test
    fun cdDotDotShortcutMovesToParentDirectory() {
        val repo = RepoState(initialized = true, currentDir = "src/main")

        val (updatedRepo, output) = GitSandboxEngine.execute(repo, "cd..")

        assertTrue(output.isEmpty())
        assertEquals("src", updatedRepo.currentDir)
    }

    @Test
    fun delIsAcceptedAsRmAlias() {
        val repo = RepoState(
            initialized = true,
            files = listOf(GitFile("deleteme.txt")),
        )

        val (updatedRepo, output) = GitSandboxEngine.execute(repo, "del deleteme.txt")

        assertTrue(output.isEmpty())
        assertFalse(updatedRepo.files.any { it.name == "deleteme.txt" })
    }
}
