package com.jetpackduba.gitnuro.viewmodels

import com.jetpackduba.gitnuro.exceptions.InvalidMessageException
import com.jetpackduba.gitnuro.exceptions.RebaseCancelledException
import com.jetpackduba.gitnuro.git.RefreshType
import com.jetpackduba.gitnuro.git.TabState
import com.jetpackduba.gitnuro.git.rebase.AbortRebaseUseCase
import com.jetpackduba.gitnuro.git.rebase.GetRebaseLinesFullMessageUseCase
import com.jetpackduba.gitnuro.git.rebase.ResumeRebaseInteractiveUseCase
import com.jetpackduba.gitnuro.git.rebase.StartRebaseInteractiveUseCase
import com.jetpackduba.gitnuro.logging.printLog
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler
import org.eclipse.jgit.lib.AbbreviatedObjectId
import org.eclipse.jgit.lib.RebaseTodoLine
import org.eclipse.jgit.lib.RebaseTodoLine.Action
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject

private const val TAG = "RebaseInteractiveViewMo"

class RebaseInteractiveViewModel @Inject constructor(
    private val tabState: TabState,
    private val getRebaseLinesFullMessageUseCase: GetRebaseLinesFullMessageUseCase,
    private val startRebaseInteractiveUseCase: StartRebaseInteractiveUseCase,
    private val abortRebaseUseCase: AbortRebaseUseCase,
    private val resumeRebaseInteractiveUseCase: ResumeRebaseInteractiveUseCase,
) {
    private val rebaseInteractiveMutex = Mutex(true)
    private val _rebaseState = MutableStateFlow<RebaseInteractiveState>(RebaseInteractiveState.Loading)
    val rebaseState: StateFlow<RebaseInteractiveState> = _rebaseState
    var rewordSteps = ArrayDeque<RebaseTodoLine>()

    private var cancelled = false
    private var completed = false

    private var interactiveHandler = object : InteractiveHandler {
        override fun prepareSteps(steps: MutableList<RebaseTodoLine>) = runBlocking {
            println("prepareSteps started")
            tabState.refreshData(RefreshType.REPO_STATE)

            val messages = getRebaseLinesFullMessageUseCase(tabState.git, steps)

            _rebaseState.value = RebaseInteractiveState.Loaded(steps, messages)

            println("prepareSteps mutex lock")
            rebaseInteractiveMutex.lock()

            if (cancelled) {
                throw RebaseCancelledException("Rebase cancelled due to user request")
            }

            val rebaseState = _rebaseState.value
            if (rebaseState !is RebaseInteractiveState.Loaded) {
                throw Exception("prepareSteps called when rebaseState is not Loaded") // Should never happen, just in case
            }

            val newSteps = rebaseState.stepsList
            rewordSteps = ArrayDeque(newSteps.filter { it.action == Action.REWORD })

            steps.clear()
            steps.addAll(newSteps)
            println("prepareSteps finished")
        }

        override fun modifyCommitMessage(commit: String): String = runBlocking {
            // This can be called when there aren't any reword steps if squash is used.
            val step = rewordSteps.removeLastOrNull() ?: return@runBlocking commit

            val rebaseState = _rebaseState.value
            if (rebaseState !is RebaseInteractiveState.Loaded) {
                throw Exception("modifyCommitMessage called when rebaseState is not Loaded") // Should never happen, just in case
            }

            return@runBlocking rebaseState.messages[step.commit.name()]
                ?: throw InvalidMessageException("Message for commit $commit is unexpectedly null")
        }
    }

    suspend fun startRebaseInteractive(revCommit: RevCommit) = tabState.runOperation(
        refreshType = RefreshType.ALL_DATA,
        showError = true
    ) { git ->
        try {
            startRebaseInteractiveUseCase(git, interactiveHandler, revCommit)
            completed = true
        } catch (ex: Exception) {
            if (ex is RebaseCancelledException) {
                println("Rebase cancelled")
            } else {
                cancel()
                throw ex
            }
        }
    }

    fun continueRebaseInteractive() = tabState.runOperation(
        refreshType = RefreshType.ONLY_LOG,
    ) {
        rebaseInteractiveMutex.unlock()
    }

    fun onCommitMessageChanged(commit: AbbreviatedObjectId, message: String) {
        val rebaseState = _rebaseState.value

        if (rebaseState !is RebaseInteractiveState.Loaded)
            return

        val messagesMap = rebaseState.messages.toMutableMap()
        messagesMap[commit.name()] = message

        _rebaseState.value = rebaseState.copy(messages = messagesMap)
    }

    fun onCommitActionChanged(commit: AbbreviatedObjectId, action: Action) {
        val rebaseState = _rebaseState.value

        if (rebaseState !is RebaseInteractiveState.Loaded)
            return

        val newStepsList =
            rebaseState.stepsList.toMutableList() // Change the list reference to update the flow with .toList()

        val stepIndex = newStepsList.indexOfFirst {
            it.commit == commit
        }

        if (stepIndex >= 0) {
            val step = newStepsList[stepIndex]
            val newTodoLine = RebaseTodoLine(action, step.commit, step.shortMessage)
            newStepsList[stepIndex] = newTodoLine

            _rebaseState.value = rebaseState.copy(stepsList = newStepsList)
        }
    }

    fun cancel() = tabState.runOperation(
        refreshType = RefreshType.REPO_STATE
    ) { git ->
        if (!cancelled && !completed) {
            abortRebaseUseCase(git)

            cancelled = true

            rebaseInteractiveMutex.unlock()
        }
    }

    fun resumeRebase() = tabState.runOperation(
        showError = true,
        refreshType = RefreshType.NONE,
    ) { git ->
        try {
            resumeRebaseInteractiveUseCase(git, interactiveHandler)
            completed = true
        } catch (ex: Exception) {
            if (ex is RebaseCancelledException) {
                println("Rebase cancelled")
            } else {
                cancel()
                throw ex
            }
        }
    }
}


sealed interface RebaseInteractiveState {
    object Loading : RebaseInteractiveState
    data class Loaded(val stepsList: List<RebaseTodoLine>, val messages: Map<String, String>) : RebaseInteractiveState
    data class Failed(val error: String) : RebaseInteractiveState
}