package com.pixelized.desktop.lwa

import androidx.lifecycle.ViewModel
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository
import com.pixelized.desktop.lwa.repository.item.ItemRepository
import com.pixelized.desktop.lwa.repository.map.MapRepository
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.repository.tag.TagRepository
import com.pixelized.shared.lwa.protocol.websocket.GameAdminEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach

class DataSyncViewModel(
    private val mapRepository: MapRepository,
    private val characterRepository: CharacterSheetRepository,
    private val inventoryRepository: InventoryRepository,
    private val alterationRepository: AlterationRepository,
    private val itemRepository: ItemRepository,
    private val campaignRepository: CampaignRepository,
    private val tagRepository: TagRepository,
    private val settingsRepository: SettingsRepository,
    private val networkRepository: NetworkRepository,
) : ViewModel() {

    fun autoConnect() {
        val settings = settingsRepository.settings()

        if (settings.playerName.isEmpty()) return

        networkRepository.connect(
            host = settings.network.host,
            port = settings.network.port,
        )
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    suspend fun synchronise() = coroutineScope {

        networkRepository.data
            .onEach { println(it) }
            .launchIn(this)

        networkRepository.status
            .filter { status -> status == NetworkRepository.Status.CONNECTED }
            .flatMapLatest { networkRepository.data }
            .filterIsInstance(GameAdminEvent.ServerSynchronization::class)
            .flowOn(context = Dispatchers.IO)
            .onEach {
                updateRepository()
                updateCharacterInstance(
                    instances = campaignRepository.campaignFlow().value.instances
                )
            }
            .launchIn(this)

        networkRepository.status
            .filter { status -> status == NetworkRepository.Status.CONNECTED }
            .flowOn(context = Dispatchers.IO)
            .onEach { updateRepository() }
            .launchIn(this)

        networkRepository.status
            .filter { status -> status == NetworkRepository.Status.CONNECTED }
            .flowOn(context = Dispatchers.IO)
            .flatMapLatest { campaignRepository.campaignFlow().map { it.instances } }
            .distinctUntilChanged()
            .onEach { updateCharacterInstance(instances = it) }
            .launchIn(this)
    }

    private suspend fun updateRepository() {
        try {
            tagRepository.updateAlterationTags()
            alterationRepository.updateAlterationFlow()
            tagRepository.updateCharacterTags()
            characterRepository.updateCharacterPreviews()
            campaignRepository.updateCampaign()
            tagRepository.updateItemTags()
            itemRepository.updateItemFlow()
            mapRepository.updateMapFlow()
        } catch (exception: Exception) {
            println(exception.message) // TODO proper exception handling
        }
    }

    private suspend fun updateCharacterInstance(
        instances: Set<String>,
    ) {
        instances.forEach { characterSheetId ->
            try {
                characterRepository.updateCharacterSheet(
                    characterSheetId = characterSheetId,
                )
                inventoryRepository.updateInventoryFlow(
                    characterSheetId = characterSheetId,
                )
            } catch (exception: Exception) {
                println(exception.message) // TODO proper exception handling
            }
        }
    }
}