package com.pixelized.desktop.lwa.repository.characterSheet

import com.pixelized.desktop.lwa.network.LwaClient
import com.pixelized.desktop.lwa.repository.alteration.AlterationStore
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetPreview
import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonFactory
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json

class CharacterSheetStore(
    private val alterationStore: AlterationStore,
    private val network: NetworkRepository,
    private val factory: CharacterSheetJsonFactory,
    private val client: LwaClient,
) {
    private val _previewFlow = MutableStateFlow<List<CharacterSheetPreview>>(value = emptyList())
    val previewFlow: StateFlow<List<CharacterSheetPreview>> get() = _previewFlow

    private val _detailFlow = MutableStateFlow<Map<String, CharacterSheet>>(value = emptyMap())
    val detailFlow: StateFlow<Map<String, CharacterSheet>> get() = _detailFlow

    init {
        val scope = CoroutineScope(Dispatchers.IO + Job())
        // data update through WebSocket.
        scope.launch {
            network.data.collect(::handleMessage)
        }
    }

    // region Rest

    suspend fun updateCharactersPreviewFlow() {
        _previewFlow.value = try {
            getCharacterPreviews()
        } catch (exception: Exception) {
            println(exception.message) // TODO proper exception handling
            emptyList()
        }
    }

    suspend fun updateCharacterSheetDetailFlow(
        characterSheetId: String,
    ) {
        val characterSheet = try {
            getCharacterSheet(characterSheetId = characterSheetId)
        } catch (exception: Exception) {
            println(exception.message) // TODO proper exception handling
            null
        }
        if (characterSheet == null) return
        _detailFlow.update { data ->
            data.toMutableMap().also {
                it[characterSheetId] = characterSheet
            }
        }
    }

    @Throws
    suspend fun getCharacterPreviews(): List<CharacterSheetPreview> {
        val request = client.getCharacterPreviews()
        return when (request.success) {
            true -> request.data
                ?.map { factory.convertFromJson(it) }
                ?: emptyList()

            else -> LwaClient.error(error = request)
        }
    }

    @Throws
    suspend fun getCharacterPreview(
        characterSheetId: String,
    ): CharacterSheetPreview {
        val request = client.getCharacterPreview(characterSheetId = characterSheetId)
        return when (request.success) {
            true -> factory.convertFromJson(request.data!!)
            else -> LwaClient.error(error = request)
        }
    }

    @Throws
    suspend fun getCharacterSheet(
        characterSheetId: String,
    ): CharacterSheet {
        val request = client.getCharacter(characterSheetId = characterSheetId)
        return when (request.success) {
            true -> request.data!!.let { factory.convertFromJson(json = it) }
            else -> LwaClient.error(error = request)
        }
    }

    @Throws
    suspend fun updateCharacterSheet(
        sheet: CharacterSheet,
        create: Boolean,
    ) {
        val json = factory.convertToJson(sheet = sheet)
        val request = client.putCharacter(sheet = json, create = create)
        if (request.success.not()) {
            LwaClient.error(error = request)
        }
    }

    @Throws
    suspend fun deleteCharacterSheet(
        characterSheetId: String,
    ) {
        val request = client.deleteCharacterSheet(characterSheetId = characterSheetId)
        if (request.success.not()) {
            LwaClient.error(error = request)
        }
    }

    @Throws
    suspend fun putCharacterAlteration(
        characterSheetId: String,
        alterationId: String,
        active: Boolean,
    ) {
        val request = client.putCharacterAlteration(
            characterSheetId = characterSheetId,
            alterationId = alterationId,
            active = active,
        )
        if (request.success.not()) {
            LwaClient.error(error = request)
        }
    }

    // endregion
    // region: WebSocket & data update.

    private suspend fun handleMessage(message: SocketMessage) {
        when (message) {
            is ApiSynchronisation.CharacterSheetApiSynchronisation -> try {
                when (message) {
                    is ApiSynchronisation.CharacterSheetUpdate -> {
                        val characterSheet = getCharacterSheet(
                            characterSheetId = message.characterSheetId,
                        )
                        _detailFlow.update(
                            sheet = characterSheet
                        )
                        _previewFlow.update(
                            preview = factory.convertToPreview(sheet = characterSheet),
                        )
                    }

                    is ApiSynchronisation.CharacterSheetDelete -> {
                        _previewFlow.update { previews ->
                            previews.toMutableList().also { sheet ->
                                sheet.removeIf {
                                    it.characterSheetId == message.characterSheetId
                                }
                            }
                        }
                        _detailFlow.update { sheets ->
                            sheets.toMutableMap().also { it.remove(message.characterSheetId) }
                        }
                    }
                }
            } catch (exception: Exception) {
                println(exception.message) // TODO proper exception handling
            }

            is CharacterSheetEvent -> when (message) {
                is CharacterSheetEvent.UpdateAlteration -> updateAlterations(
                    characterSheetId = message.characterSheetId,
                    alterationId = message.alterationId,
                    active = message.active,
                )

                is CharacterSheetEvent.UpdateDamage -> updateDamage(
                    characterSheetId = message.characterSheetId,
                    damage = message.damage
                )

                is CharacterSheetEvent.UpdateDiminished -> updateDiminished(
                    characterSheetId = message.characterSheetId,
                    diminished = message.diminished
                )

                is CharacterSheetEvent.UpdateFatigue -> updateFatigue(
                    characterSheetId = message.characterSheetId,
                    fatigue = message.fatigue
                )

                is CharacterSheetEvent.UpdateSkillUsageEvent -> updateCharacterSkillChange(
                    characterSheetId = message.characterSheetId,
                    skillId = message.skillId,
                    used = message.used,
                )
            }

            else -> Unit
        }
    }

    private suspend fun updateCharacterSkillChange(
        characterSheetId: String,
        skillId: String,
        used: Boolean,
    ) {
        val sheet = getCharacterSheet(characterSheetId = characterSheetId)
        val character = sheet.copy(
            commonSkills = sheet.commonSkills.map { skill ->
                skill.takeIf { skill.id == skillId }?.copy(used = used) ?: skill
            },
            specialSkills = sheet.specialSkills.map { skill ->
                skill.takeIf { skill.id == skillId }?.copy(used = used) ?: skill
            },
            magicSkills = sheet.magicSkills.map { skill ->
                skill.takeIf { skill.id == skillId }?.copy(used = used) ?: skill
            },
        )
        _detailFlow.update(character)
    }

    private fun updateAlterations(
        characterSheetId: String,
        alterationId: String,
        active: Boolean,
    ) {
        // check if the alteration exist
        alterationStore.alteration(alterationId = alterationId) ?: return
        // check if the character exit (detail may have not been downloaded)
        val sheet = detailFlow.value[characterSheetId] ?: return
        // check if the alteration is already active or not on this character.
        val containAlteration = sheet.alterations.contains(alterationId)

        if (containAlteration.not() && active) {
            _detailFlow.update(
                sheet = sheet.copy(
                    alterations = sheet.alterations.toMutableList().also { it.add(alterationId) }
                )
            )
        }
        if (containAlteration && active.not()) {
            _detailFlow.update(
                sheet = sheet.copy(
                    alterations = sheet.alterations.toMutableList().also { it.remove(alterationId) }
                )
            )
        }
    }

    private suspend fun updateDamage(
        characterSheetId: String,
        damage: Int,
    ) {
        val sheet = getCharacterSheet(characterSheetId = characterSheetId)
        _detailFlow.update(sheet.copy(damage = damage))
    }

    private suspend fun updateDiminished(
        characterSheetId: String,
        diminished: Int,
    ) {
        val sheet = getCharacterSheet(characterSheetId = characterSheetId)
        _detailFlow.update(sheet.copy(diminished = diminished))
    }

    private suspend fun updateFatigue(
        characterSheetId: String,
        fatigue: Int,
    ) {
        val sheet = getCharacterSheet(characterSheetId = characterSheetId)
        _detailFlow.update(sheet.copy(fatigue = fatigue))
    }

    // endregion

    private fun MutableStateFlow<Map<String, CharacterSheet>>.update(
        sheet: CharacterSheet,
    ): CharacterSheet {
        value = value.toMutableMap().also {
            it[sheet.id] = sheet
        }
        return sheet
    }

    private fun MutableStateFlow<List<CharacterSheetPreview>>.update(
        preview: CharacterSheetPreview,
    ): CharacterSheetPreview {
        val index = value.indexOfFirst {
            it.characterSheetId == preview.characterSheetId
        }
        value = if (index >= 0) {
            value.toMutableList().also { it[index] = preview }
        } else {
            value.toMutableList().also { it.add(preview) }
        }
        return preview
    }
}