package com.pixelized.desktop.lwa.repository.inventory

import com.pixelized.desktop.lwa.network.LwaClient
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.shared.lwa.model.inventory.Inventory
import com.pixelized.shared.lwa.model.inventory.factory.InventoryJsonFactory
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 InventoryStore(
    private val network: NetworkRepository,
    private val factory: InventoryJsonFactory,
    private val client: LwaClient,
) {
    private val _inventories = MutableStateFlow<Map<String, Inventory>>(emptyMap())
    val inventories: StateFlow<Map<String, Inventory>> = _inventories

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

    suspend fun updateInventoryFlow(
        characterSheetId: String,
    ) {
        val inventory = try {
            getInventory(characterSheetId = characterSheetId)
        } catch (exception: Exception) {
            println(exception.message) // TODO proper exception handling
            null
        }
        // update the flow with the new item.
        _inventories.update { items ->
            items.toMutableMap().also {
                it[characterSheetId] = inventory
                    ?: Inventory.empty(characterSheetId = characterSheetId)
            }
        }
    }

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

    @Throws
    suspend fun putInventory(
        inventory: Inventory,
        create: Boolean,
    ) {
        val request = client.putInventory(
            inventory = factory.convertToJson(inventory = inventory),
            create = create,
        )
        if (request.success.not()) {
            LwaClient.error(error = request)
        }
    }

    @Throws
    suspend fun putInventoryPurse(
        characterSheetId: String,
        purse: Inventory.Purse,
    ) {
        val request = client.putInventoryPurse(
            purse = factory.convertToApiPurse(
                characterSheetId = characterSheetId,
                purse = purse,
            ),
        )
        if (request.success.not()) {
            LwaClient.error(error = request)
        }
    }

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

    @Throws
    suspend fun createInventoryItem(
        characterSheetId: String,
        itemId: String,
        count: Float,
    ): String {
        val request = client.createInventoryItem(
            characterSheetId = characterSheetId,
            itemId = itemId,
            count = count
        )
        if (request.success.not()) {
            LwaClient.error(error = request)
        }
        return request.data!!
    }

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

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

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

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

    // region: WebSocket & data update.

    private suspend fun handleMessage(message: SocketMessage) {
        when (message) {
            is ApiSynchronisation.InventoryApiSynchronisation -> when (message) {
                is ApiSynchronisation.PurseUpdate -> _inventories.update {
                    it.toMutableMap().also { inventories ->
                        inventories[message.characterSheetId]?.let { inventory ->
                            inventories[message.characterSheetId] = inventory.copy(
                                purse = Inventory.Purse(
                                    gold = inventory.purse.gold + message.gold,
                                    silver = inventory.purse.silver + message.silver,
                                    copper = inventory.purse.copper + message.copper,
                                )
                            )
                        }
                    }
                }

                is ApiSynchronisation.InventoryUpdate -> updateInventoryFlow(
                    characterSheetId = message.characterSheetId,
                )

                is ApiSynchronisation.InventoryDelete -> _inventories.update { items ->
                    items.toMutableMap().also {
                        it.remove(message.characterSheetId)
                    }
                }
            }

            else -> Unit
        }
    }

    // endregion
}