package bou.amine.apps.readerforselfossv2.model

import bou.amine.apps.readerforselfossv2.utils.DateUtils
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.encodeCollection
import kotlinx.serialization.json.*

class SelfossModel {
    @Serializable
    data class Tag(
        val tag: String,
        val color: String,
        val unread: Int,
    )

    @Serializable
    class Stats(
        val total: Int,
        val unread: Int? = null,
        val starred: Int? = null,
    )

    @Serializable
    data class Spout(
        val name: String,
        val description: String,
    )

    @Serializable
    data class ApiInformation(
        val version: String? = null,
        val apiversion: String? = null,
        val configuration: ApiConfiguration? = null,
    ) {
        fun getApiMajorVersion(): Int {
            var versionNumber = 0
            if (apiversion != null) {
                versionNumber = apiversion.substringBefore(".").toInt()
            }
            return versionNumber
        }

        fun getApiConfiguration() = configuration ?: ApiConfiguration(null, null)
    }

    @Serializable
    data class ApiConfiguration(
        @Serializable(with = BooleanSerializer::class)
        val publicMode: Boolean? = null,
        @Serializable(with = BooleanSerializer::class)
        val authEnabled: Boolean? = null,
    ) {
        fun isAuthEnabled() = authEnabled ?: true

        fun isPublicModeEnabled() = publicMode ?: false
    }

    interface Source {
        val id: Int
        var title: String
        var unread: Int?
        var error: String?
        var icon: String?
    }

    @Serializable
    data class SourceStats(
        override val id: Int,
        override var title: String,
        override var unread: Int? = null,
        override var error: String? = null,
        override var icon: String? = null,
    ) : Source

    @Serializable
    data class SourceDetail(
        override val id: Int,
        override var title: String,
        override var unread: Int? = null,
        @Serializable(with = TagsListSerializer::class)
        var tags: List<String>? = null,
        var spout: String? = null,
        override var error: String? = null,
        override var icon: String? = null,
        var params: SourceParams? = null,
    ) : Source

    @Serializable
    data class SourceParams(
        val url: String? = null,
    )

    @Serializable
    data class Item(
        val id: Int,
        val datetime: String,
        val title: String,
        val content: String,
        @Serializable(with = BooleanSerializer::class)
        var unread: Boolean,
        @Serializable(with = BooleanSerializer::class)
        var starred: Boolean,
        val thumbnail: String? = null,
        val icon: String? = null,
        val link: String,
        val sourcetitle: String,
        @Serializable(with = TagsListSerializer::class)
        val tags: List<String>,
        val author: String? = null,
    ) {
        fun getLinkDecoded(): String {
            var stringUrl: String
            stringUrl =
                if (link.contains("//news.google.com/news/") && link.contains("&amp;url=")) {
                    link.substringAfter("&amp;url=")
                } else {
                    this.link.replace("&amp;", "&")
                }

            // handle :443 => https
            if (stringUrl.contains(":443")) {
                stringUrl = stringUrl.replace(":443", "").replace("http://", "https://")
            }

            // handle url not starting with http
            if (stringUrl.startsWith("//")) {
                stringUrl = "http:$stringUrl"
            }

            return stringUrl
        }

        fun sourceAuthorAndDate(): String {
            var txt = this.sourcetitle.getHtmlDecoded()
            if (!this.author.isNullOrBlank()) {
                txt += " (by ${this.author}) "
            }
            txt += DateUtils.parseRelativeDate(this.datetime)
            return txt
        }

        fun sourceAuthorOnly(): String {
            var txt = this.sourcetitle.getHtmlDecoded()
            if (!this.author.isNullOrBlank()) {
                txt += " (by ${this.author}) "
            }
            return txt
        }

        fun toggleStar(): Item {
            this.starred = !this.starred
            return this
        }
    }

    // TODO: this seems to be super slow.
    object TagsListSerializer : KSerializer<List<String>> {
        override fun deserialize(decoder: Decoder): List<String> {
            return when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {
                is JsonArray -> json.toList().map { it.toString().replace("^\"|\"$".toRegex(), "") }
                else -> json.toString().split(",")
            }
        }

        override val descriptor: SerialDescriptor
            get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)

        override fun serialize(
            encoder: Encoder,
            value: List<String>,
        ) {
            encoder.encodeCollection(PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING), value.size) { this.toString() }
        }
    }

    object BooleanSerializer : KSerializer<Boolean> {
        override fun deserialize(decoder: Decoder): Boolean {
            val json = ((decoder as JsonDecoder).decodeJsonElement()).jsonPrimitive
            return if (json.booleanOrNull != null) {
                json.boolean
            } else {
                json.int == 1
            }
        }

        override val descriptor: SerialDescriptor
            get() = PrimitiveSerialDescriptor("b", PrimitiveKind.BOOLEAN)

        override fun serialize(
            encoder: Encoder,
            value: Boolean,
        ) {
            TODO("Not yet implemented")
        }
    }
}
