package theorycrafter

import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.*
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.IOException
import okio.use
import theorycrafter.utils.ValueOrError
import java.text.DateFormat
import java.text.SimpleDateFormat


/**
 * The types of Theorycrafter releases.
 */
@Serializable
enum class ReleaseType(val id: String, val displayName: String) {

    @SerialName("stable")
    Stable("stable", displayName = "Stable"),

    @SerialName("beta")
    Beta("beta", displayName = "Beta"),

    @SerialName("dev")
    Development("dev", displayName = "Development");

    override fun toString() = displayName

    companion object {

        /**
         * Returns the [ReleaseType] with the given id.
         */
        fun byId(id: String): ReleaseType = entries.first { it.id == id }

    }

}


/**
 * The information provided about a release.
 */
class ReleaseInfo(private val json: JsonObject) {

    val timestampSeconds: Long
        get() = json["timestamp"]!!.jsonPrimitive.long

    val versionCode: Int
        get() = json["versionCode"]!!.jsonPrimitive.int

    val versionName: String
        get() = json["versionName"]!!.jsonPrimitive.content

    val releaseNote: String
        get() = json["releaseNote"]!!.jsonPrimitive.content

    val infoUrl: String
        get() = json["infoUrl"]!!.jsonPrimitive.content

    val displayReleaseDate: String by lazy {
        THEORYCRAFTER_RELEASE_DATE_FORMAT.format(timestampSeconds * 1000)
    }

}


/**
 * Returns the [ReleaseInfo] of the given type specified by a [JsonObject].
 */
private fun JsonObject.releaseInfo(type: ReleaseType): ReleaseInfo? {
    return this[type.id]?.jsonObject?.let { ReleaseInfo(it) }
}


/**
 * The set of latest Theorycrafter releases.
 */
class TheorycrafterReleases(json: JsonObject) {

    val development: ReleaseInfo? = json.releaseInfo(ReleaseType.Development)
    val beta: ReleaseInfo? = json.releaseInfo(ReleaseType.Beta)
    val stable: ReleaseInfo? = json.releaseInfo(ReleaseType.Stable)

    operator fun get(releaseType: ReleaseType) = when (releaseType) {
        ReleaseType.Development -> development
        ReleaseType.Beta -> beta
        ReleaseType.Stable -> stable
    }

}


/**
 * Loads and returns the latest [TheorycrafterReleases].
 */
fun loadLatestTheorycrafterReleases(): ValueOrError<TheorycrafterReleases, String> {
    val request: Request = Request.Builder()
        .url(Theorycrafter.ReleasesInfoUrl)
        .build()

    try {
        OkHttpClient().newCall(request).execute().use { response ->
            val responseBody = response.body
            if (response.code != 200)
                throw IOException("Response code ${response.code}")
            else if (responseBody == null)
                throw IOException("Missing response body")

            val json = (Json.parseToJsonElement(responseBody.string()) as JsonObject)
            return ValueOrError.success(TheorycrafterReleases(json))
        }
    } catch (e: Exception) {
        return ValueOrError.failure("Error retrieving latest version information:\n${e.message}")
    }
}


/**
 * Loads the latest [TheorycrafterReleases] as Compose state.
 */
@Composable
fun loadLatestTheorycrafterReleasesAsState(): State<ValueOrError<TheorycrafterReleases, String>?> {
    return produceState(null) {
        value = withContext(Dispatchers.IO) {
            loadLatestTheorycrafterReleases()
        }
    }
}

/**
 * The formatter for release date.
 */
val THEORYCRAFTER_RELEASE_DATE_FORMAT: DateFormat = SimpleDateFormat.getDateInstance()