package com.pixelized.server.lwa.services.campaign

import com.pixelized.server.lwa.server.exception.FileReadException
import com.pixelized.server.lwa.server.exception.FileWriteException
import com.pixelized.server.lwa.server.exception.JsonCodingException
import com.pixelized.server.lwa.server.exception.JsonConversionException
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.campaign.CampaignJson
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
import com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import java.io.File

class CampaignStore(
    private val pathProvider: PathProvider,
    private val factory: CampaignJsonFactory,
    private val jsonSerializer: Json,
    scope: CoroutineScope,
) {
    private val campaignFlow = MutableStateFlow(value = Campaign.empty())

    init {
        // create the directory if needed.
        File(pathProvider.campaignPath()).also { it.mkdirs() }
        // load the initial data
        scope.launch {
            updateCampaignFlow()
        }
    }

    fun campaignFlow(): StateFlow<Campaign> = campaignFlow

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

    @Throws(
        FileReadException::class,
        JsonCodingException::class,
        JsonConversionException::class,
    )
    suspend fun load(): Campaign {
        return withContext(Dispatchers.IO) {
            val file = campaignFile()
            // Read the campaign file.
            val data = try {
                file.readText(charset = Charsets.UTF_8)
            } catch (exception: Exception) {
                throw FileReadException(root = exception)
            }
            // Guard, if the file is empty we load a default campaign.
            if (data.isBlank()) return@withContext Campaign.empty()
            // Decode the json into a string.
            val json = try {
                jsonSerializer.decodeFromString<CampaignJson>(data)
            } catch (exception: Exception) {
                throw JsonCodingException(root = exception)
            }
            // Convert from the Json format
            val campaign = try {
                factory.convertFromJson(json)
            } catch (exception: Exception) {
                throw JsonConversionException(root = exception)
            }
            return@withContext campaign
        }
    }

    @Throws(
        JsonConversionException::class,
        JsonCodingException::class,
        FileWriteException::class,
    )
    suspend fun save(campaign: Campaign) {
        withContext(Dispatchers.IO) {
            // Transform the json into the model.
            val json = try {
                factory.convertToJson(campaign = campaign)
            } catch (exception: Exception) {
                throw JsonConversionException(root = exception)
            }
            // Encode the json into a string.
            val data = try {
                jsonSerializer.encodeToString(json)
            } catch (exception: Exception) {
                throw JsonCodingException(root = exception)
            }
            // Write the file
            try {
                val file = campaignFile()
                file.writeText(
                    text = data,
                    charset = Charsets.UTF_8,
                )
            } catch (exception: Exception) {
                throw FileWriteException(root = exception)
            }
            // Update the dataflow.
            campaignFlow.update { campaign }
        }
    }

    private fun campaignFile(): File {
        return File("${pathProvider.campaignPath()}campaign.json")
    }
}