package com.pixelized.desktop.lwa.ui.overlay.roll

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.ui.overlay.roll.CriticalityUio.Criticality
import com.pixelized.desktop.lwa.ui.overlay.roll.DifficultyUio.Difficulty
import com.pixelized.shared.lwa.model.AlteredCharacterSheet
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
import com.pixelized.shared.lwa.protocol.websocket.RollEvent
import com.pixelized.shared.lwa.usecase.ExpressionUseCase
import com.pixelized.shared.lwa.usecase.SkillStepUseCase
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.roll_page__critical_failure
import lwacharactersheet.composeapp.generated.resources.roll_page__critical_success
import lwacharactersheet.composeapp.generated.resources.roll_page__dc_easy__label
import lwacharactersheet.composeapp.generated.resources.roll_page__dc_hard__label
import lwacharactersheet.composeapp.generated.resources.roll_page__dc_impossible__label
import lwacharactersheet.composeapp.generated.resources.roll_page__dc_normal__label
import lwacharactersheet.composeapp.generated.resources.roll_page__failure
import lwacharactersheet.composeapp.generated.resources.roll_page__special_success
import lwacharactersheet.composeapp.generated.resources.roll_page__success
import org.jetbrains.compose.resources.getString
import java.util.UUID

class RollViewModel(
    private val characterSheetRepository: CharacterSheetRepository,
    private val alterationRepository: AlterationRepository,
    private val skillComputation: ExpressionUseCase,
    private val skillStepUseCase: SkillStepUseCase,
    private val networkRepository: NetworkRepository,
    private val alteredCharacterSheetFactory: AlteredCharacterSheetFactory,
) : ViewModel() {
    private var alteredCharacterSheet: AlteredCharacterSheet? = null
    private var rollAction: RollAction.Uio? = null

    var lastRollResult: RollResult = RollResult.Dismissed
        private set

    private var rollJob: Job? = null

    val rollRotation = Animatable(0f)
    val rollScale = Animatable(1f)

    private val _cancellable = mutableStateOf(true)
    val cancellable: State<Boolean> get() = _cancellable

    private val _rollTitle = mutableStateOf<RollTitleUio?>(null)
    val rollTitle: State<RollTitleUio?> get() = _rollTitle

    private val _rollResult = mutableStateOf<RollResultUio?>(null)
    val rollResult: State<RollResultUio?> get() = _rollResult

    private val _rollDifficulty = mutableStateOf<DifficultyUio?>(null)
    val rollDifficulty: State<DifficultyUio?> get() = _rollDifficulty

    private val _rollCriticality = mutableStateOf<CriticalityUio?>(null)
    val rollCriticality: State<CriticalityUio?> get() = _rollCriticality

    private val _shareResult = mutableStateOf(true)
    val shareResult: State<Boolean> = _shareResult

    suspend fun cleanRoll() {
        alteredCharacterSheet = null
        rollAction = null

        lastRollResult = RollResult.Dismissed

        rollRotation.snapTo(0f)
        rollScale.snapTo(1f)

        _shareResult.value = true
        _cancellable.value = true
        _rollTitle.value = null
        _rollResult.value = null
        _rollDifficulty.value = null
    }

    /**
     * prepare the this ViewModel to roll.
     * return true if the viewModel is ready to roll, otherwise false.
     */
    suspend fun prepareRoll(
        roll: RollAction.Uio,
    ) {
        rollRotation.snapTo(0f)
        rollScale.snapTo(1f)
        lastRollResult = RollResult.Dismissed

        val characterSheet = characterSheetRepository.characterDetail(
            characterSheetId = roll.characterSheetId,
        )

        if (characterSheet == null) return

        val alterations = alterationRepository.activeFieldAlterations(
            characterSheetId = roll.characterSheetId,
        )

        this.alteredCharacterSheet = alteredCharacterSheetFactory.sheet(
            characterSheet = characterSheet,
            alterations = alterations,
        )

        this.rollAction = roll

        val rollStep = roll
            .let { it as? RollAction.Uio.BoundedRollActionUio }
            ?.rollSuccessValue
            ?.let { skillStepUseCase.computeSkillStep(skill = it) }

        _shareResult.value = true
        _cancellable.value = true
        _rollResult.value = null

        _rollTitle.value = RollTitleUio(
            label = roll.label,
            value = rollStep?.success?.last
        )

        _rollDifficulty.value = roll
            .let { it as? RollAction.Uio.BoundedRollActionUio }
            ?.rollSuccessValue
            ?.let {
                DifficultyUio(
                    open = false,
                    difficulty = Difficulty.NORMAL,
                )
            }

        _rollCriticality.value = roll
            .let { it as? RollAction.Uio.BoundlessRollActionUio }
            ?.takeIf { it.canBeCritical }
            ?.let {
                CriticalityUio(
                    open = false,
                    criticality = Criticality.NORMAL
                )
            }
    }

    suspend fun roll() {
        if (!cancellable.value) return
        val alteredCharacterSheet = alteredCharacterSheet ?: return
        val rollAction = rollAction ?: return
        val rollTitle = _rollTitle.value ?: return

        coroutineScope {
            _rollResult.value = null

            rollJob?.cancel()
            rollJob = launch {
                launch {
                    diceScaleAnimation()
                }
                launch {
                    diceRotationAnimation()
                }
                launch {
                    delay(500)
                    _cancellable.value = false
                    // compute the skill critical success to critical failure ranges.
                    val rollStep = rollAction
                        .let { it as? RollAction.Uio.BoundedRollActionUio }
                        ?.rollSuccessValue?.let {
                            skillStepUseCase.computeSkillStep(
                                skill = when (_rollDifficulty.value?.difficulty) {
                                    Difficulty.EASY -> it * 2
                                    Difficulty.NORMAL -> it
                                    Difficulty.HARD -> it / 2
                                    Difficulty.IMPOSSIBLE -> it / 4
                                    else -> it
                                }
                            )
                        }

                    // compute the expression.
                    val expression = when (rollAction) {
                        is RollAction.Uio.BoundedRollActionUio -> rollAction.rollAction
                        is RollAction.Uio.BoundlessRollActionUio -> when (rollCriticality.value?.criticality) {
                            Criticality.NORMAL -> rollAction.rollDefaultAction
                            Criticality.SPECIAL -> rollAction.rollSpecialAction
                            Criticality.CRITICAL -> rollAction.rollCriticalAction
                            null -> null
                        }
                    } ?: ""
                    // compute the roll (typically use the expression inside the rollAction)
                    val roll = skillComputation.computeRoll(
                        sheet = alteredCharacterSheet,
                        expression = expression,
                    )

                    // check where the roll fall into the rollSteps.
                    val resultLabel = rollStep?.let {
                        when (roll) {
                            in it.criticalSuccess -> getString(resource = Res.string.roll_page__critical_success)
                            in it.specialSuccess -> getString(resource = Res.string.roll_page__special_success)
                            in it.success -> getString(resource = Res.string.roll_page__success)
                            in it.failure -> getString(resource = Res.string.roll_page__failure)
                            in it.criticalFailure -> getString(resource = Res.string.roll_page__critical_failure)
                            else -> ""
                        }
                    }

                    // build the roll result.
                    lastRollResult = if (rollStep != null) {
                        RollResult.BoundedRollResult(
                            result = when (roll) {
                                in rollStep.criticalSuccess -> RollResult.BoundedRollResult.Result.CriticalSuccess
                                in rollStep.specialSuccess -> RollResult.BoundedRollResult.Result.SpecialSuccess
                                in rollStep.success -> RollResult.BoundedRollResult.Result.Success
                                in rollStep.criticalFailure -> RollResult.BoundedRollResult.Result.CriticalFailure
                                else -> RollResult.BoundedRollResult.Result.Failure
                            },
                            difficulty = when (_rollDifficulty.value?.difficulty) {
                                Difficulty.IMPOSSIBLE -> RollResult.BoundedRollResult.Difficulty.Impossible
                                Difficulty.HARD -> RollResult.BoundedRollResult.Difficulty.Hard
                                Difficulty.NORMAL -> RollResult.BoundedRollResult.Difficulty.Normal
                                else -> RollResult.BoundedRollResult.Difficulty.Easy
                            }
                        )

                    } else {
                        RollResult.BoundlessRollResult(
                            value = roll,
                        )
                    }

                    _rollResult.value = RollResultUio(
                        label = resultLabel ?: "",
                        value = roll,
                    )

                    if (shareResult.value) {
                        launch {
                            shareRollResult(
                                rollTitle = rollTitle,
                                roll = roll,
                                rollStep = rollStep,
                                success = resultLabel
                            )
                        }
                    }
                }
            }
        }
    }

    fun toggleShareResult() {
        _shareResult.value = _shareResult.value.not()
    }

    fun toggleDifficulty() {
        _rollDifficulty.value = _rollDifficulty.value?.copy(
            open = _rollDifficulty.value?.open?.not() ?: false
        )
    }

    fun onDifficulty(difficulty: Difficulty) {
        _rollDifficulty.value = DifficultyUio(
            open = false,
            difficulty = difficulty,
        )
        val rollStep = rollAction
            .let { it as? RollAction.Uio.BoundedRollActionUio }
            ?.rollSuccessValue
            ?.let {
                skillStepUseCase.computeSkillStep(
                    skill = when (_rollDifficulty.value?.difficulty) {
                        Difficulty.EASY -> it * 2
                        Difficulty.NORMAL -> it
                        Difficulty.HARD -> it / 2
                        Difficulty.IMPOSSIBLE -> it / 4
                        else -> it
                    }
                )
            }
        _rollTitle.value = _rollTitle.value?.copy(
            value = rollStep?.success?.last
        )
    }

    fun toggleCriticality() {
        _rollCriticality.value = _rollCriticality.value?.copy(
            open = _rollCriticality.value?.open?.not() ?: false
        )
    }

    fun onCriticality(criticality: Criticality) {
        _rollCriticality.value = CriticalityUio(
            open = false,
            criticality = criticality,
        )
    }

    private suspend fun diceRotationAnimation() {
        rollRotation.animateTo(
            targetValue = rollRotation.value.let { it - it % 360 } + 360f * 3,
            animationSpec = spring(
                dampingRatio = Spring.DampingRatioNoBouncy,
                stiffness = Spring.StiffnessLow,
            )
        )
    }

    private suspend fun diceScaleAnimation() {
        rollScale.animateTo(
            targetValue = 1.20f,
            animationSpec = spring(
                dampingRatio = Spring.DampingRatioNoBouncy,
                stiffness = 800f,
            )
        )
        rollScale.animateTo(
            targetValue = 1f,
            animationSpec = spring(
                dampingRatio = 0.28f,
                stiffness = 800f,
            )
        )
    }

    private suspend fun shareRollResult(
        rollTitle: RollTitleUio,
        roll: Int,
        rollStep: SkillStepUseCase.SkillStep?,
        success: String?,
    ) {
        val rollAction = rollAction ?: return

        val payload = RollEvent(
            timestamp = System.currentTimeMillis(),
            uuid = UUID.randomUUID().toString(),
            characterSheetId = rollAction.characterSheetId,
            skillLabel = rollTitle.label,
            rollValue = roll,
            resultLabel = success,
            rollDifficulty = when (_rollDifficulty.value?.difficulty) {
                Difficulty.EASY -> getString(Res.string.roll_page__dc_easy__label)
                Difficulty.NORMAL -> getString(Res.string.roll_page__dc_normal__label)
                Difficulty.HARD -> getString(Res.string.roll_page__dc_hard__label)
                Difficulty.IMPOSSIBLE -> getString(Res.string.roll_page__dc_impossible__label)
                else -> null
            },
            rollSuccessLimit = rollStep?.success?.last,
            critical = rollStep?.let {
                when (roll) {
                    in it.criticalSuccess -> RollEvent.Critical.CRITICAL_SUCCESS
                    in it.specialSuccess -> RollEvent.Critical.SPECIAL_SUCCESS
                    in it.success -> RollEvent.Critical.SUCCESS
                    in it.failure -> RollEvent.Critical.FAILURE
                    in it.criticalFailure -> RollEvent.Critical.CRITICAL_FAILURE
                    else -> null
                }
            },
        )
        networkRepository.share(
            message = payload,
        )
    }
}