package chat.rocket.android.chatrooms.adapter

import android.app.Application
import android.text.SpannableStringBuilder
import chat.rocket.android.R
import chat.rocket.android.chatrooms.adapter.model.RoomUiModel
import chat.rocket.android.db.model.ChatRoom
import chat.rocket.android.server.domain.GetCurrentUserInteractor
import chat.rocket.android.server.domain.PermissionsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.showLastMessage
import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.domain.useSpecialCharsOnRoom
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.date
import chat.rocket.android.util.extensions.localDateTime
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.User
import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.model.userStatusOf
import chat.rocket.core.model.Room
import chat.rocket.core.model.SpotlightResult
import ru.noties.markwon.Markwon

class RoomUiModelMapper(
    private val context: Application,
    private val settings: PublicSettings,
    private val userInteractor: GetCurrentUserInteractor,
    private val serverUrl: String,
    private val permissions: PermissionsInteractor
) {
    private val currentUser by lazy { userInteractor.get() }

    fun map(
        rooms: List<ChatRoom>,
        grouped: Boolean = false,
        showLastMessage: Boolean = true
    ): List<ItemHolder<*>> {
        val list = ArrayList<ItemHolder<*>>(rooms.size + 5)
        var lastType: String? = null
        if (grouped) {
            val favRooms = rooms.filter { it.chatRoom.favorite == true }
            val unfavRooms = rooms.filterNot { it.chatRoom.favorite == true }

            if (favRooms.isNotEmpty()) {
                list.add(HeaderItemHolder(context.resources.getString(R.string.header_favorite)))
            }
            favRooms.forEach { room ->
                list.add(RoomItemHolder(map(room, showLastMessage)))
            }
            unfavRooms.forEach { room ->
                if (lastType != room.chatRoom.type) {
                    list.add(HeaderItemHolder(roomType(room.chatRoom.type)))
                }
                list.add(RoomItemHolder(map(room, showLastMessage)))
                lastType = room.chatRoom.type
            }
        } else {
            rooms.forEach { room ->
                list.add(RoomItemHolder(map(room, showLastMessage)))
            }
        }

        return list
    }

    fun map(spotlight: SpotlightResult, showLastMessage: Boolean = true): List<ItemHolder<*>> {

        val list = ArrayList<ItemHolder<*>>(spotlight.users.size + spotlight.rooms.size)
        spotlight.users.filterNot { it.username.isNullOrEmpty() }.forEach { user ->
            list.add(RoomItemHolder(mapUser(user)))
        }
        spotlight.rooms.filterNot { it.name.isNullOrEmpty() }.forEach { room ->
            list.add(RoomItemHolder(mapRoom(room, showLastMessage)))
        }

        return list
    }

    private fun mapUser(user: User): RoomUiModel = with(user) {
        val name = mapName(user.username!!, user.name)
        val status = user.status
        val avatar = serverUrl.avatarUrl(user.username!!)
        val username = user.username!!

        RoomUiModel(
                id = user.id,
                name = name,
                type = roomTypeOf(RoomType.DIRECT_MESSAGE),
                avatar = avatar,
                status = status,
                username = username
        )
    }

    private fun mapRoom(room: Room, showLastMessage: Boolean = true): RoomUiModel = with(room) {
        RoomUiModel(
                id = id,
                name = name!!,
                type = type,
                avatar = serverUrl.avatarUrl(name!!, isGroupOrChannel = true),
                lastMessage = if (showLastMessage) {
                    mapLastMessage(
                            lastMessage?.sender?.id, lastMessage?.sender?.username,
                            lastMessage?.sender?.name, lastMessage?.message,
                            isDirectMessage = type is RoomType.DirectMessage
                    )
                } else {
                    null
                },
                muted = muted.orEmpty(),
                writable = isChannelWritable(muted)
        )
    }

    fun map(chatRoom: ChatRoom, showLastMessage: Boolean = true): RoomUiModel = with(chatRoom.chatRoom) {
        val isUnread = alert || unread > 0
        val type = roomTypeOf(type)
        val status = chatRoom.status?.let { userStatusOf(it) }
        val roomName = mapName(name, fullname)
        val favorite = favorite
        val timestamp = mapDate(lastMessageTimestamp ?: updatedAt)
        val avatar = if (type is RoomType.DirectMessage) {
            serverUrl.avatarUrl(name)
        } else {
            serverUrl.avatarUrl(name, isGroupOrChannel = true)
        }
        val unread = mapUnread(unread)
        val lastMessage = if (showLastMessage) {
            mapLastMessage(
                    lastMessageUserId,
                    chatRoom.lastMessageUserName,
                    chatRoom.lastMessageUserFullName,
                    lastMessageText,
                    type is RoomType.DirectMessage
            )
        } else {
            null
        }
        val hasMentions = mapMentions(userMentions, groupMentions)
        val open = open
        val lastMessageMarkdown = lastMessage?.let { Markwon.markdown(context, it.toString()).toString() }

        RoomUiModel(
                id = id,
                name = roomName,
                type = type,
                avatar = avatar,
                open = open,
                date = timestamp,
                unread = unread,
                mentions = hasMentions,
                favorite = favorite,
                alert = isUnread,
                lastMessage = lastMessageMarkdown,
                status = status,
                username = if (type is RoomType.DirectMessage) name else null,
                muted = muted.orEmpty(),
                writable = isChannelWritable(muted)
        )
    }

    private fun isChannelWritable(muted: List<String>?): Boolean {
        val canWriteToReadOnlyChannels = permissions.canPostToReadOnlyChannels()
        return canWriteToReadOnlyChannels || !muted.orEmpty().contains(currentUser?.username)
    }

    private fun roomType(type: String): String = with(context.resources) {
        when (type) {
            RoomType.CHANNEL -> getString(R.string.header_channel)
            RoomType.PRIVATE_GROUP -> getString(R.string.header_private_groups)
            RoomType.DIRECT_MESSAGE -> getString(R.string.header_direct_messages)
            RoomType.LIVECHAT -> getString(R.string.header_live_chats)
            else -> getString(R.string.header_unknown)
        }
    }

    private fun mapLastMessage(
        userId: String?,
        name: String?,
        fullName: String?,
        text: String?,
        isDirectMessage: Boolean = false
    ): CharSequence? {
        return if (!settings.showLastMessage()) {
            null
        } else if (name != null && text != null) {
            val user = if (currentUser != null && currentUser!!.id == userId) {
                "${context.getString(R.string.msg_you)}: "
            } else {
                if (isDirectMessage) "" else "${mapName(name, fullName)}: "
            }
            SpannableStringBuilder().append(user).append(text)
        } else {
            context.getText(R.string.msg_no_messages_yet)
        }
    }

    private fun mapName(name: String, fullName: String?): CharSequence {
        return if (settings.useSpecialCharsOnRoom() || settings.useRealName()) {
            fullName ?: name
        } else {
            name
        }
    }

    private fun mapUnread(unread: Long): String? = when (unread) {
        0L -> null
        in 1..99 -> unread.toString()
        else -> context.getString(R.string.msg_more_than_ninety_nine_unread_messages)

    }

    private fun mapMentions(userMentions: Long?, groupMentions: Long?): Boolean {
        if (userMentions != null && groupMentions != null) {
            if (userMentions > 0 || groupMentions > 0) {
                return true
            }
        }
        return false
    }

    private fun mapDate(date: Long?): CharSequence? = date?.localDateTime()?.date(context)
}
