package com.pixelized.server.lwa.services.tag

import com.pixelized.server.lwa.server.exception.FileReadException
import com.pixelized.server.lwa.server.exception.FileWriteException
import com.pixelized.server.lwa.server.exception.JsonConversionException
import com.pixelized.shared.lwa.model.tag.TagJson
import com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import java.io.File

private const val CHARACTER = "character"
private const val ALTERATION = "alteration"
private const val ITEM = "item"

class TagStore(
    private val pathProvider: PathProvider,
    private val jsonSerializer: Json,
    scope: CoroutineScope,
) {
    private val alterationTagsFlow = MutableStateFlow<Map<String, TagJson>>(emptyMap())
    private val characterTagsFlow = MutableStateFlow<Map<String, TagJson>>(emptyMap())
    private val itemTagsFlow = MutableStateFlow<Map<String, TagJson>>(emptyMap())

    init {
        // make the file path.
        File(pathProvider.tagsPath()).mkdirs()
        // load the initial data
        scope.launch {
            updateTagFlow()
        }
    }

    fun alterationTags(): StateFlow<Map<String, TagJson>> = alterationTagsFlow
    fun characterTags(): StateFlow<Map<String, TagJson>> = characterTagsFlow
    fun itemTags(): StateFlow<Map<String, TagJson>> = itemTagsFlow

    suspend fun updateTagFlow() {
        update(
            flow = alterationTagsFlow,
            file = alterationFile(),
        )
        update(
            flow = characterTagsFlow,
            file = characterFile(),
        )
        update(
            flow = itemTagsFlow,
            file = itemFile(),
        )
    }

    private suspend fun update(
        flow: MutableStateFlow<Map<String, TagJson>>,
        file: File,
    ) {
        flow.value = try {
            file.readTags().associateBy { it.id }
        } catch (exception: Exception) {
            println(exception.message) // TODO proper exception handling
            emptyMap()
        }
    }

    @Throws(FileReadException::class, JsonConversionException::class)
    private suspend fun File.readTags(): List<TagJson> {
        // read the file (force the UTF8 format)
        val data = try {
            readText(charset = Charsets.UTF_8)
        } catch (exception: Exception) {
            throw FileReadException(root = exception)
        }
        // Guard, if the json is blank no alteration have been save, ignore this file.
        if (data.isBlank()) {
            return emptyList()
        }
        return try {
            jsonSerializer.decodeFromString<List<TagJson>>(data)
        } catch (exception: Exception) {
            throw JsonConversionException(
                root = exception
            )
        }
    }

    @Throws(JsonConversionException::class, FileWriteException::class)
    private suspend fun saveAlterationTags(tags: List<TagJson>) {
        // convert the data to json format
        val json = try {
            this.jsonSerializer.encodeToString(tags)
        } catch (exception: Exception) {
            throw JsonConversionException(root = exception)
        }
        // write the file
        try {
            val file = alterationFile()
            file.writeText(
                text = json,
                charset = Charsets.UTF_8,
            )
        } catch (exception: Exception) {
            throw FileWriteException(root = exception)
        }
        // Update the dataflow.
        alterationTagsFlow.value = tags.associateBy { it.id }
    }

    private fun characterFile() = File("${pathProvider.tagsPath()}$CHARACTER.json")
    private fun alterationFile() = File("${pathProvider.tagsPath()}$ALTERATION.json")
    private fun itemFile() = File("${pathProvider.tagsPath()}$ITEM.json")
}