package com.pixelized.desktop.lwa.repository.alteration

import com.pixelized.desktop.lwa.network.LwaClient
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.shared.lwa.model.alteration.Alteration
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation.AlterationApiSynchronisation
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 AlterationStore(
    private val network: NetworkRepository,
    private val factory: AlterationJsonFactory,
    private val client: LwaClient,
) {
    private val _alterationsFlow = MutableStateFlow<Map<String, Alteration>>(emptyMap())
    val alterationsFlow: StateFlow<Map<String, Alteration>> = _alterationsFlow

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

    fun alterations(): Collection<Alteration> {
        return alterationsFlow.value.values
    }

    fun alteration(alterationId: String): Alteration? {
        return alterationsFlow.value[alterationId]
    }

    suspend fun updateAlterationsFlow() {
        _alterationsFlow.value = try {
            getAlteration()
        } catch (exception: Exception) {
            println(exception.message) // TODO proper exception handling
            emptyMap()
        }
    }

    private suspend fun updateAlterationFlow(
        alterationId: String,
    ) {
        val alteration = try {
            getAlteration(alterationId = alterationId)
        } catch (exception: Exception) {
            println(exception.message) // TODO proper exception handling
            null
        }
        // guard case if getAlteration failed
        if (alteration == null) return
        // update the flow with the alteration.
        _alterationsFlow.update { alterations ->
            alterations.toMutableMap().also {
                it[alterationId] = alteration
            }
        }
    }

    @Throws
    private suspend fun getAlteration(): Map<String, Alteration> {
        val request = client.getAlterations()
        return when (request.success) {
            true -> request.data
                ?.map { factory.convertFromJson(json = it) }
                ?.associateBy { it.id }
                ?: emptyMap()

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

    @Throws
    private suspend fun getAlteration(
        alterationId: String,
    ): Alteration? {
        val request = client.getAlteration(alterationId = alterationId)
        return when (request.success) {
            true -> request.data?.let { factory.convertFromJson(json = it) }
            else -> LwaClient.error(error = request)
        }
    }

    @Throws
    suspend fun putAlteration(
        alteration: Alteration,
        create: Boolean,
    ) {
        val request = client.putAlteration(
            alterationJson = factory.convertToJson(data = alteration),
            create = create,
        )
        if (request.success.not()) {
            LwaClient.error(error = request)
        }
    }

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

    // region: WebSocket & data update.

    private suspend fun handleMessage(message: SocketMessage) {
        when (message) {
            is AlterationApiSynchronisation -> when (message) {
                is ApiSynchronisation.AlterationUpdate -> updateAlterationFlow(
                    alterationId = message.alterationId,
                )

                is ApiSynchronisation.AlterationDelete -> _alterationsFlow.update { alterations ->
                    alterations.toMutableMap().also {
                        it.remove(message.alterationId)
                    }
                }
            }

            else -> Unit
        }
    }

    // endregion
}