package dev.inmo.tgbotapi.types.update

import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.queries.callback.RawCallbackQuery
import dev.inmo.tgbotapi.types.InlineQueries.ChosenInlineResult.RawChosenInlineResult
import dev.inmo.tgbotapi.types.InlineQueries.query.RawInlineQuery
import dev.inmo.tgbotapi.types.chat.ChatJoinRequest
import dev.inmo.tgbotapi.types.chat.member.ChatMemberUpdated
import dev.inmo.tgbotapi.types.message.abstracts.*
import dev.inmo.tgbotapi.types.payments.PreCheckoutQuery
import dev.inmo.tgbotapi.types.payments.ShippingQuery
import dev.inmo.tgbotapi.types.polls.Poll
import dev.inmo.tgbotapi.types.polls.PollAnswer
import dev.inmo.tgbotapi.types.update.abstracts.UnknownUpdate
import dev.inmo.tgbotapi.types.update.abstracts.Update
import kotlinx.serialization.*
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonObject

@Serializable
internal data class RawUpdate constructor(
    @SerialName(updateIdField)
    val updateId: UpdateIdentifier,
    @Serializable(TelegramBotAPIMessageDeserializeOnlySerializer::class)
    private val edited_message: CommonMessage<*>? = null,
    @Serializable(TelegramBotAPIMessageDeserializeOnlySerializer::class)
    private val message: Message? = null,
    @Serializable(TelegramBotAPIMessageDeserializeOnlySerializer::class)
    private val edited_channel_post: CommonMessage<*>? = null,
    @Serializable(TelegramBotAPIMessageDeserializeOnlySerializer::class)
    private val channel_post: Message? = null,
    private val inline_query: RawInlineQuery? = null,
    private val chosen_inline_result: RawChosenInlineResult? = null,
    private val callback_query: RawCallbackQuery? = null,
    private val shipping_query: ShippingQuery? = null,
    private val pre_checkout_query: PreCheckoutQuery? = null,
    private val poll: Poll? = null,
    private val poll_answer: PollAnswer? = null,
    private val my_chat_member: ChatMemberUpdated? = null,
    private val chat_member: ChatMemberUpdated? = null,
    private val chat_join_request: ChatJoinRequest? = null
) {
    private var initedUpdate: Update? = null
    /**
     * @return One of children of [Update] interface or null in case of unknown type of update
     */
    fun asUpdate(raw: JsonElement): Update {
        return initedUpdate ?: try {
            when {
                edited_message != null -> EditMessageUpdate(updateId, edited_message)
                message != null -> MessageUpdate(updateId, message)
                edited_channel_post != null -> EditChannelPostUpdate(updateId, edited_channel_post)
                channel_post != null -> ChannelPostUpdate(updateId, channel_post)

                chosen_inline_result != null -> ChosenInlineResultUpdate(updateId, chosen_inline_result.asChosenInlineResult)
                inline_query != null -> InlineQueryUpdate(updateId, inline_query.asInlineQuery)
                callback_query != null -> CallbackQueryUpdate(
                    updateId,
                    callback_query.asCallbackQuery(raw.jsonObject["callback_query"].toString())
                )
                shipping_query != null -> ShippingQueryUpdate(updateId, shipping_query)
                pre_checkout_query != null -> PreCheckoutQueryUpdate(updateId, pre_checkout_query)
                poll != null -> PollUpdate(updateId, poll)
                poll_answer != null -> PollAnswerUpdate(updateId, poll_answer)
                my_chat_member != null -> MyChatMemberUpdatedUpdate(updateId, my_chat_member)
                chat_member != null -> CommonChatMemberUpdatedUpdate(updateId, chat_member)
                chat_join_request != null -> ChatJoinRequestUpdate(updateId, chat_join_request)
                else -> UnknownUpdate(
                    updateId,
                    raw.toString(),
                    raw
                )
            }
        } catch (e: Error) {
            when (e) {
                is SerializationException,
                is NotImplementedError -> UnknownUpdate(
                    updateId,
                    raw.toString(),
                    raw
                )
                else -> throw e
            }
        }.also {
            initedUpdate = it
        }
    }
}
