package com.pixelized.desktop.lwa.repository.item

import com.pixelized.desktop.lwa.network.LwaClient
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.shared.lwa.model.item.Item
import com.pixelized.shared.lwa.model.item.factory.ItemJsonFactory
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
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 ItemStore(
    private val network: NetworkRepository,
    private val factory: ItemJsonFactory,
    private val client: LwaClient,
) {
    private val _items = MutableStateFlow<Map<String, Item>>(emptyMap())
    val items: StateFlow<Map<String, Item>> get() = _items

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

    fun items(): Collection<Item> {
        return items.value.values
    }

    fun item(itemId: String): Item? {
        return items.value[itemId]
    }

    suspend fun updateItemFlow() {
        _items.value = try {
            getItem()
        } catch (exception: Exception) {
            println(exception.message) // TODO proper exception handling
            emptyMap()
        }
    }

    private suspend fun updateItemFlow(
        itemId: String,
    ) {
        val item = try {
            getItem(itemId = itemId)
        } catch (exception: Exception) {
            println(exception.message) // TODO proper exception handling
            null
        }
        // guard case if getItem failed
        if (item == null) return
        // update the flow with the new item.
        _items.update { items ->
            items.toMutableMap().also {
                it[itemId] = item
            }
        }
    }

    @Throws
    private suspend fun getItem(): Map<String, Item> {
        val request = client.getItems()
        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 getItem(
        itemId: String,
    ): Item? {
        val request = client.getItem(itemId = itemId)
        return when (request.success) {
            true -> request.data?.let { factory.convertFromJson(json = it) }
            else -> LwaClient.error(error = request)
        }
    }

    @Throws
    suspend fun putItem(
        item: Item,
        create: Boolean,
    ) {
        val request = client.putItem(
            itemJson = factory.convertToJson(item = item),
            create = create,
        )
        if (request.success.not()) {
            LwaClient.error(error = request)
        }
    }

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

    // region: WebSocket & data update.

    private suspend fun handleMessage(message: SocketMessage) {
        when (message) {
            is ApiSynchronisation.ItemApiSynchronisation -> when (message) {
                is ApiSynchronisation.ItemUpdate -> updateItemFlow(
                    itemId = message.itemId,
                )

                is ApiSynchronisation.ItemDelete -> _items.update { items ->
                    items.toMutableMap().also {
                        it.remove(message.itemId)
                    }
                }
            }

            else -> Unit
        }
    }

    // endregion
}