package com.pixelized.desktop.lwa.repository.campaign

import com.pixelized.desktop.lwa.network.LwaClient
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.campaign.Campaign.*
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
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

class CampaignStore(
    private val network: NetworkRepository,
    private val factory: CampaignJsonFactory,
    private val client: LwaClient,
) {
    private val campaignFlow = MutableStateFlow(value = Campaign.empty())

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

    fun campaignFlow(): StateFlow<Campaign> = campaignFlow

    suspend fun updateCampaignFlow() {
        campaignFlow.value = try {
            getCampaign()
        } catch (exception: Exception) {
            println(exception.message) // TODO proper exception handling
            Campaign.empty()
        }
    }

    @Throws
    suspend fun changeSceneTitle(
        title: String,
    ) {
        val scene = factory.createScene(title = title)
        val request = client.putCampaignScene(scene = scene)
        if (request.success.not()) {
            LwaClient.error(error = request)
        }
    }

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

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

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

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

    @Throws
    private suspend fun getCampaign(): Campaign {
        val request = client.getCampaign()
        return when (request.success) {
            true -> request.data
                ?.let { factory.convertFromJson(json = it) }
                ?: Campaign.empty()

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

    // region : WebSocket message Handling.

    private fun handleMessage(message: SocketMessage) {
        when (message) {
            is CampaignEvent -> {
                when (message) {
                    is CampaignEvent.CharacterAdded -> campaignFlow.update { campaign ->
                        campaign.copy(
                            characters = campaign.characters.toMutableSet().also {
                                it.add(message.characterSheetId)
                            }
                        )
                    }

                    is CampaignEvent.CharacterRemoved -> campaignFlow.update { campaign ->
                        campaign.copy(
                            characters = campaign.characters.toMutableSet().also {
                                it.remove(message.characterSheetId)
                            }
                        )
                    }

                    is CampaignEvent.NpcAdded -> campaignFlow.update { campaign ->
                        campaign.copy(
                            npcs = campaign.npcs.toMutableSet().also {
                                it.add(message.characterSheetId)
                            }
                        )
                    }

                    is CampaignEvent.NpcRemoved -> campaignFlow.update { campaign ->
                        campaign.copy(
                            npcs = campaign.npcs.toMutableSet().also {
                                it.remove(message.characterSheetId)
                            }
                        )
                    }

                    is CampaignEvent.SceneUpdated -> campaignFlow.update { campaign ->
                        campaign.copy(
                            scene = Scene(name = message.name)
                        )
                    }

                    is CampaignEvent.MapUpdated -> campaignFlow.update { campaign ->
                        campaign.copy(
                            map = factory.convertFromJson(message.map),
                        )
                    }

                    is CampaignEvent.MapDeleted -> campaignFlow.update { campaign ->
                        campaign.copy(map = null,)
                    }
                }
            }

            is GameMasterEvent -> when (message) {
                is GameMasterEvent.ToggleNpc -> {
                    campaignFlow.value = campaignFlow.value.copy(
                        options = campaignFlow.value.options.copy(
                            showNpcs = campaignFlow.value.options.showNpcs.not()
                        )
                    )
                }

                is GameMasterEvent.TogglePlayer -> {
                    campaignFlow.value = campaignFlow.value.copy(
                        options = campaignFlow.value.options.copy(
                            showParty = campaignFlow.value.options.showParty.not()
                        )
                    )
                }

                is GameMasterEvent.DisplayPortrait -> Unit // nothing to do here
            }

            else -> Unit
        }
    }

    // endregion
}
