package kr.co.vividnext.sodalive.live.room

import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.Service
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.graphics.Rect
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.Spannable
import android.text.SpannableString
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.view.LayoutInflater
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.transform.CircleCropTransformation
import com.github.dhaval2404.imagepicker.ImagePicker
import com.google.gson.Gson
import com.orhanobut.logger.Logger
import io.agora.rtc2.ClientRoleOptions
import io.agora.rtc2.IRtcEngineEventHandler
import io.agora.rtc2.RtcConnection
import io.agora.rtm.RtmChannelAttribute
import io.agora.rtm.RtmChannelListener
import io.agora.rtm.RtmChannelMember
import io.agora.rtm.RtmClientListener
import io.agora.rtm.RtmMessage
import io.agora.rtm.RtmMessageType
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.agora.Agora
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.RealPathUtil
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.common.SodaLiveService
import kr.co.vividnext.sodalive.databinding.ActivityLiveRoomBinding
import kr.co.vividnext.sodalive.dialog.LiveDialog
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.moneyFormat
import kr.co.vividnext.sodalive.live.room.chat.LiveRoomChatAdapter
import kr.co.vividnext.sodalive.live.room.chat.LiveRoomChatRawMessage
import kr.co.vividnext.sodalive.live.room.chat.LiveRoomChatRawMessageType
import kr.co.vividnext.sodalive.live.room.chat.LiveRoomDonationChat
import kr.co.vividnext.sodalive.live.room.chat.LiveRoomDonationStatusChat
import kr.co.vividnext.sodalive.live.room.chat.LiveRoomJoinChat
import kr.co.vividnext.sodalive.live.room.chat.LiveRoomNormalChat
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationDialog
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageDialog
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessageViewModel
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationRankingDialog
import kr.co.vividnext.sodalive.live.room.info.GetRoomInfoResponse
import kr.co.vividnext.sodalive.live.room.profile.LiveRoomProfileDialog
import kr.co.vividnext.sodalive.live.room.profile.LiveRoomProfileListAdapter
import kr.co.vividnext.sodalive.live.room.profile.LiveRoomUserProfileDialog
import kr.co.vividnext.sodalive.live.room.update.LiveRoomInfoEditDialog
import kr.co.vividnext.sodalive.report.ProfileReportDialog
import kr.co.vividnext.sodalive.report.ReportType
import kr.co.vividnext.sodalive.report.UserReportDialog
import kr.co.vividnext.sodalive.settings.notification.MemberRole
import org.koin.android.ext.android.inject
import java.util.regex.Pattern

class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomBinding::inflate) {

    private var roomId: Long = 0

    private val viewModel: LiveRoomViewModel by inject()
    private val donationMessageViewModel: LiveRoomDonationMessageViewModel by inject()

    private lateinit var speakerListAdapter: LiveRoomProfileListAdapter
    private lateinit var loadingDialog: LoadingDialog

    private lateinit var imm: InputMethodManager
    private val handler = Handler(Looper.getMainLooper())

    private val chatAdapter = LiveRoomChatAdapter { userId ->
        showLiveRoomUserProfileDialog(userId = userId)
    }
    private lateinit var layoutManager: LinearLayoutManager

    private lateinit var agora: Agora
    private lateinit var roomDialog: LiveRoomDialog
    private lateinit var roomProfileDialog: LiveRoomProfileDialog
    private lateinit var roomInfoEditDialog: LiveRoomInfoEditDialog
    private lateinit var roomUserProfileDialog: LiveRoomUserProfileDialog

    private var isSpeakerMute = false
    private var isMicrophoneMute = false
    private var isSpeaker = false
    private var isSpeakerFold = false
    private var isAvailableDonation = false

    private val onBackPressedCallback = object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            onClickQuit()
        }
    }

    private val imageResult =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            val resultCode = result.resultCode
            val data = result.data

            if (resultCode == RESULT_OK) {
                // Image Uri will not be null for RESULT_OK
                val fileUri = data?.data!!
                roomInfoEditDialog.setCoverImageUri(fileUri)
            } else if (resultCode == ImagePicker.RESULT_ERROR) {
                Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        agora = Agora(
            context = this,
            rtcEventHandler = rtcEventHandler,
            rtmClientListener = rtmClientListener
        )

        super.onCreate(savedInstanceState)
        onBackPressedDispatcher.addCallback(this, onBackPressedCallback)

        viewModel.getRealPathFromURI = {
            RealPathUtil.getRealPath(applicationContext, it)
        }
        this.roomId = intent.getLongExtra(Constants.EXTRA_ROOM_ID, 0)

        if (roomId <= 0) {
            showToast("해당하는 라이브가 없습니다.")
            finish()
            return
        }

        viewModel.getMemberCan()
        viewModel.getRoomInfo(roomId)
    }

    override fun onStart() {
        super.onStart()

        if (this::layoutManager.isInitialized) {
            layoutManager.scrollToPosition(chatAdapter.itemCount - 1)
        }

        if (
            viewModel.isRoomInfoInitialized() &&
            agora.getConnectionState() ==
            RtcConnection.CONNECTION_STATE_TYPE.CONNECTION_STATE_DISCONNECTED.ordinal
        ) {
            val userId = SharedPreferenceManager.userId
            agora.joinRtcChannel(
                uid = userId.toInt(),
                rtcToken = viewModel.roomInfoResponse.rtcToken,
                channelName = viewModel.roomInfoResponse.channelName
            )
        }
    }

    override fun setupView() {
        bindData()

        loadingDialog = LoadingDialog(this, layoutInflater)
        imm = getSystemService(
            Service.INPUT_METHOD_SERVICE
        ) as InputMethodManager

        roomDialog = LiveRoomDialog(this, layoutInflater)
        roomInfoEditDialog = LiveRoomInfoEditDialog(
            activity = this,
            layoutInflater = layoutInflater,
            onClickImagePicker = {
                ImagePicker.with(this)
                    .crop()
                    .galleryOnly()
                    .galleryMimeTypes( // Exclude gif images
                        mimeTypes = arrayOf(
                            "image/png",
                            "image/jpg",
                            "image/jpeg"
                        )
                    )
                    .createIntent { imageResult.launch(it) }
            }
        )
        roomProfileDialog = LiveRoomProfileDialog(
            layoutInflater = layoutInflater,
            activity = this,
            roomInfoLiveData = viewModel.roomInfoLiveData,
            isStaff = {
                viewModel.isEqualToManagerId(
                    memberId = SharedPreferenceManager.userId.toInt()
                )
            },
            onClickInviteSpeaker = { memberId ->
                if (speakerListAdapter.itemCount <= 9) {
                    inviteSpeaker(memberId)
                } else {
                    showToast("스피커 정원이 초과했습니다.")
                }
            },
            onClickChangeListener = { memberId ->
                if (memberId == SharedPreferenceManager.userId) {
                    handler.post {
                        viewModel.setListener(
                            roomId,
                            SharedPreferenceManager.userId
                        ) {
                            setAudience()
                            viewModel.getRoomInfo(roomId)
                        }
                    }

                    return@LiveRoomProfileDialog
                }

                changeListenerMessage(memberId)
            },
            onClickKickOut = {
                LiveDialog(
                    activity = this,
                    layoutInflater = layoutInflater,
                    title = "내보내기",
                    desc = "${viewModel.getUserNickname(it.toInt())}님을 내보내시겠어요?",
                    confirmButtonTitle = "내보내기",
                    confirmButtonClick = { kickOut(it) },
                    cancelButtonTitle = "취소",
                    cancelButtonClick = {}
                ).show(screenWidth)
            },
            onClickProfile = {
                showLiveRoomUserProfileDialog(userId = it)
            }
        )

        roomUserProfileDialog = LiveRoomUserProfileDialog(
            activity = this,
            userProfileLiveData = viewModel.userProfileLiveData,
            layoutInflater = layoutInflater,
            isStaff = {
                viewModel.isEqualToManagerId(it.toInt())
            },
            onClickSendMessage = { userId, nickname ->
            },
            onClickSetManager = {
                setManagerMessageToPeer(userId = it)
                viewModel.setManager(roomId = roomId, userId = it) {
                    setManagerMessage()
                    showDialog(
                        content = "${viewModel.getUserNickname(it.toInt())}님을 스탭으로 지정했습니다."
                    )
                }
            },
            onClickReleaseManager = {
                changeListenerMessage(it, isFromManager = true)
            },
            onClickFollow = {
                viewModel.creatorFollow(
                    creatorId = it,
                    roomId = roomId,
                    isGetUserProfile = true
                )
            },
            onClickUnFollow = {
                viewModel.creatorUnFollow(
                    creatorId = it,
                    roomId = roomId,
                    isGetUserProfile = true
                )
            },
            onClickInviteSpeaker = {
                if (speakerListAdapter.itemCount <= 9) {
                    inviteSpeaker(it)
                } else {
                    showToast("스피커 정원이 초과했습니다.")
                }
            },
            onClickChangeListener = { changeListenerMessage(it) },
            onClickKickOut = {
                LiveDialog(
                    activity = this,
                    layoutInflater = layoutInflater,
                    title = "내보내기",
                    desc = "${viewModel.getUserNickname(it.toInt())}님을 내보내시겠어요?",
                    confirmButtonTitle = "내보내기",
                    confirmButtonClick = { kickOut(it) },
                    cancelButtonTitle = "취소",
                    cancelButtonClick = {}
                ).show(screenWidth)
            },
            onClickPopupMenu = { userId, nickname, isBlock, view ->
                showOptionMenu(
                    this,
                    userId,
                    nickname,
                    isBlock,
                    view,
                )
            }
        )

        binding.tvQuit.setOnClickListener { onClickQuit() }
        binding.flMicrophoneMute.setOnClickListener {
            microphoneMute()
            if (isMicrophoneMute) {
                binding.ivMicrophoneMute.setImageResource(R.drawable.ic_mic_off)
                binding.ivNotiMicrophoneMute.visibility = View.VISIBLE
            } else {
                binding.ivMicrophoneMute.setImageResource(R.drawable.ic_mic_on)
                binding.ivNotiMicrophoneMute.visibility = View.GONE
            }
        }
        binding.flSpeakerMute.setOnClickListener {
            speakerMute()
            if (isSpeakerMute) {
                binding.ivSpeakerMute.setImageResource(R.drawable.ic_speaker_off)
            } else {
                binding.ivSpeakerMute.setImageResource(R.drawable.ic_speaker_on)
            }
        }
        binding.ivSend.setOnClickListener { inputChat() }
        binding.flDonation.setOnClickListener {
            val dialog = LiveRoomDonationDialog(
                this,
                LayoutInflater.from(this)
            ) { can, message ->
                if (can > 0) {
                    donation(can, message)
                } else {
                    showToast("1캔 이상 후원하실 수 있습니다.")
                }
            }

            dialog.show(screenWidth)
        }
        binding.ivNotification.setOnClickListener { viewModel.toggleShowNotice() }
        binding.rlNotice.setOnClickListener { viewModel.toggleExpandNotice() }
        binding.tvSpeakerFold.setOnClickListener {
            isSpeakerFold = !isSpeakerFold

            if (isSpeakerFold) {
                binding.rlSpeaker.visibility = View.VISIBLE
                binding.rvSpeakers.visibility = View.VISIBLE
                binding.tvSpeakerFold.text = "접기"
                binding.tvSpeakerFold.setCompoundDrawablesWithIntrinsicBounds(
                    R.drawable.ic_live_detail_top,
                    0,
                    0,
                    0
                )
            } else {
                binding.rlSpeaker.visibility = View.GONE
                binding.rvSpeakers.visibility = View.GONE
                binding.tvSpeakerFold.text = "펼치기"
                binding.tvSpeakerFold.setCompoundDrawablesWithIntrinsicBounds(
                    R.drawable.ic_live_detail_bottom,
                    0,
                    0,
                    0
                )
            }
        }

        binding.tvBgSwitch.setOnClickListener { viewModel.toggleBackgroundImage() }
        binding.llDonation.setOnClickListener {
            LiveRoomDonationRankingDialog(
                activity = this,
                layoutInflater = layoutInflater,
                viewModel = viewModel,
                roomId = roomId
            ).show()
        }

        setupChatAdapter()
        setupSpeakerListAdapter()
    }

    override fun onDestroy() {
        hideKeyboard {
            viewModel.quitRoom(roomId) {
                SodaLiveService.stopService(this)
                agora.deInitAgoraEngine()
            }
        }
        super.onDestroy()
    }

    private fun showOptionMenu(
        context: Context,
        userId: Long,
        nickname: String,
        isBlock: Boolean,
        v: View
    ) {
        val popup = PopupMenu(context, v)
        val inflater = popup.menuInflater

        if (isBlock) {
            inflater.inflate(R.menu.user_profile_option_menu_2, popup.menu)

            popup.setOnMenuItemClickListener {
                when (it.itemId) {
                    R.id.menu_user_block -> {
                        viewModel.memberUnBlock(userId)
                    }

                    R.id.menu_user_report -> {
                        showUserReportDialog(userId)
                    }

                    R.id.menu_profile_report -> {
                        showProfileReportDialog(userId)
                    }
                }

                true
            }
        } else {
            inflater.inflate(R.menu.user_profile_option_menu, popup.menu)

            popup.setOnMenuItemClickListener {
                when (it.itemId) {
                    R.id.menu_user_block -> {
                        showMemberBlockDialog(userId, nickname)
                    }

                    R.id.menu_user_report -> {
                        showUserReportDialog(userId)
                    }

                    R.id.menu_profile_report -> {
                        showProfileReportDialog(userId)
                    }
                }

                true
            }
        }

        popup.show()
    }

    private fun showMemberBlockDialog(userId: Long, nickname: String) {
        val dialog = AlertDialog.Builder(this)
        dialog.setTitle("사용자 차단")
        dialog.setMessage(
            "${nickname}님을 차단하시겠습니까?\n\n" +
                "사용자를 차단하면 사용자는 아래 기능이 제한됩니다.\n" +
                "- 내가 개설한 라이브 입장 불가\n" +
                "- 나에게 메시지 보내기 불가\n" +
                "- 내 채널의 팬Talk 작성불가"
        )
        dialog.setPositiveButton("차단") { _, _ ->
            roomUserProfileDialog.dismiss()
            viewModel.memberBlock(userId) {
                kickOut(userId)
            }
        }
        dialog.setNegativeButton("취소") { _, _ -> }
        dialog.show()
    }

    private fun showUserReportDialog(userId: Long) {
        val dialog = UserReportDialog(this, layoutInflater) {
            viewModel.report(
                type = ReportType.USER,
                userId = userId,
                reason = it
            )
        }

        dialog.show(screenWidth)
    }

    private fun showProfileReportDialog(userId: Long) {
        val dialog = ProfileReportDialog(this, layoutInflater) {
            viewModel.report(
                type = ReportType.PROFILE,
                userId = userId
            )
        }

        dialog.show(screenWidth)
    }

    private fun showLiveRoomUserProfileDialog(userId: Long) {
        viewModel.getUserProfile(roomId = roomId, userId = userId) {
            roomUserProfileDialog.show()
        }
    }

    @SuppressLint("SetTextI18n")
    private fun bindData() {
        viewModel.isBgOn.observe(this) {
            if (it) {
                binding.ivCover.visibility = View.VISIBLE
                binding.tvBgSwitch.text = "배경 ON"
                binding.tvBgSwitch.setTextColor(
                    ContextCompat.getColor(
                        applicationContext,
                        R.color.color_9970ff
                    )
                )
                binding.tvBgSwitch
                    .setBackgroundResource(R.drawable.bg_round_corner_13_3_transparent_9970ff)
            } else {
                binding.ivCover.visibility = View.GONE
                binding.tvBgSwitch.text = "배경 OFF"
                binding.tvBgSwitch.setTextColor(
                    ContextCompat.getColor(
                        applicationContext,
                        R.color.color_eeeeee
                    )
                )
                binding.tvBgSwitch
                    .setBackgroundResource(R.drawable.bg_round_corner_13_3_transparent_bbbbbb)
            }
        }

        viewModel.isLoading.observe(this) {
            if (it) {
                loadingDialog.show(screenWidth)
            } else {
                loadingDialog.dismiss()
            }
        }

        viewModel.toastLiveData.observe(this) {
            it?.let { showToast(it) }
        }

        donationMessageViewModel.isLoading.observe(this) {
            if (it) {
                loadingDialog.show(screenWidth)
            } else {
                loadingDialog.dismiss()
            }
        }

        donationMessageViewModel.toastLiveData.observe(this) {
            it?.let { showToast(it) }
        }

        viewModel.roomInfoLiveData.observe(this) { response ->
            binding.tvTitle.text = response.title
            binding.ivCover.load(response.coverImageUrl) {
                crossfade(true)
                placeholder(R.drawable.ic_logo)
            }

            isAvailableDonation = response.isAvailableDonation
            binding.flDonation.visibility = if (response.isAvailableDonation) {
                View.VISIBLE
            } else {
                View.GONE
            }

            if (
                response.managerId == SharedPreferenceManager.userId &&
                SharedPreferenceManager.role == MemberRole.CREATOR.name
            ) {
                binding.flDonationMessageList.visibility = View.VISIBLE
                binding.flDonationMessageList.setOnClickListener {
                    LiveRoomDonationMessageDialog(
                        layoutInflater = LayoutInflater.from(this),
                        activity = this,
                        donationMessageListLiveData = donationMessageViewModel
                            .donationMessageListLiveData,
                        donationMessageCountLiveData = donationMessageViewModel
                            .donationMessageCountLiveData,
                        getDonationMessageList = {
                            donationMessageViewModel.getDonationMessageList(roomId = roomId)
                        },
                        deleteDonationMessage = {
                            donationMessageViewModel.deleteDonationMessage(
                                roomId = roomId,
                                uuid = it
                            )
                        },
                        copyMessage = {
                            val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
                            clipboard.setPrimaryClip(ClipData.newPlainText(it, it))
                            showToast("후원 메시지가 복사되었습니다.")
                        }
                    ).show()
                }
            } else {
                binding.flDonationMessageList.visibility = View.GONE
            }

            speakerListAdapter.managerId = response.managerId
            speakerListAdapter.updateList(response.speakerList)

            if (response.managerId == SharedPreferenceManager.userId) {
                binding.ivEdit.setOnClickListener {
                    roomInfoEditDialog.setRoomInfo(response.title, response.notice)
                    roomInfoEditDialog.setCoverImageUrl(response.coverImageUrl)
                    roomInfoEditDialog.setConfirmAction { newTitle, newContent, newCoverImageUri ->
                        viewModel.editLiveRoomInfo(
                            response.roomId,
                            newTitle,
                            newContent,
                            newCoverImageUri,
                            onSuccess = {
                                binding.tvTitle.text = newTitle
                                setNoticeAndClickableUrl(binding.tvNotice, newContent)

                                if (newCoverImageUri != null) {
                                    binding.ivCover.load(newCoverImageUri) {
                                        crossfade(true)
                                        placeholder(R.drawable.ic_logo)
                                    }
                                }

                                agora.sendRawMessageToGroup(
                                    rawMessage = Gson().toJson(
                                        LiveRoomChatRawMessage(
                                            type = LiveRoomChatRawMessageType.EDIT_ROOM_INFO,
                                            message = "",
                                            can = 0,
                                            donationMessage = ""
                                        )
                                    ).toByteArray()
                                )
                            }
                        )
                    }

                    roomInfoEditDialog.show(screenWidth)
                }

                binding.ivEdit.visibility = View.VISIBLE
                binding.tvQuit.text = "라이브 종료"

                handler.postDelayed({
                    binding.tvQuit.requestLayout()
                    binding.ivEdit.requestLayout()
                }, 250)
            } else {
                binding.ivEdit.visibility = View.GONE
            }

            binding.ivShare.setOnClickListener {
                viewModel.shareRoomLink(
                    response.roomId,
                    response.isPrivateRoom,
                    response.password
                ) {
                    val intent = Intent(Intent.ACTION_SEND)
                    intent.type = "text/plain"
                    intent.putExtra(Intent.EXTRA_TEXT, it)

                    val shareIntent = Intent.createChooser(intent, "라이브 공유")
                    startActivity(shareIntent)
                }
            }

            binding.llViewUsers.setOnClickListener { roomProfileDialog.show() }
            binding.tvParticipate.text = "${response.participantsCount}"
            setNoticeAndClickableUrl(binding.tvNotice, response.notice)

            binding.tvCreatorNickname.text = response.managerNickname
            binding.ivCreatorProfile.load(response.managerProfileUrl) {
                crossfade(true)
                placeholder(R.drawable.ic_logo)
                transformations(CircleCropTransformation())
            }

            binding.ivCreatorProfile.setOnClickListener {
                if (response.managerId != SharedPreferenceManager.userId) {
                    showLiveRoomUserProfileDialog(userId = response.managerId)
                }
            }

            if (response.isAvailableDonation) {
                binding.ivCreatorFollow.visibility = View.VISIBLE

                if (response.isFollowingManager) {
                    binding.ivCreatorFollow.setImageResource(R.drawable.btn_following)
                    binding.ivCreatorFollow.setOnClickListener {
                        viewModel.creatorUnFollow(
                            creatorId = response.managerId,
                            roomId = roomId
                        )
                    }
                } else {
                    binding.ivCreatorFollow.setImageResource(R.drawable.btn_follow)
                    binding.ivCreatorFollow.setOnClickListener {
                        viewModel.creatorFollow(
                            creatorId = response.managerId,
                            roomId = roomId
                        )
                    }
                }
            } else {
                binding.ivCreatorFollow.visibility = View.GONE
            }

            if (agora.rtmChannelIsNull()) {
                joinChannel(response)
            }
        }

        viewModel.isShowNotice.observe(this) {
            if (it) {
                binding.ivNotification.setImageResource(R.drawable.ic_notice_selected)
                binding.rlNotice.visibility = View.VISIBLE
            } else {
                binding.ivNotification.setImageResource(R.drawable.ic_notice_normal)
                binding.rlNotice.visibility = View.GONE
            }
        }

        viewModel.isExpandNotice.observe(this) {
            binding.tvNotice.maxLines = if (it) {
                Int.MAX_VALUE
            } else {
                1
            }
        }

        viewModel.totalDonationCan.observe(this) {
            binding.tvTotalCan.text = it.moneyFormat()
        }
    }

    private fun setNoticeAndClickableUrl(textView: TextView, text: String) {
        textView.text = text

        val spannable = SpannableString(text)
        val pattern = Pattern.compile("https?://\\S+")
        val matcher = pattern.matcher(spannable)

        while (matcher.find()) {
            val start = matcher.start()
            val end = matcher.end()
            val clickableSpan = object : ClickableSpan() {
                override fun onClick(widget: View) {
                    val url = spannable.subSequence(start, end).toString()
                    startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
                }
            }
            spannable.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
        }

        textView.text = spannable
        textView.movementMethod = LinkMovementMethod.getInstance()
    }

    private fun onClickQuit() {
        hideKeyboard {
            if (viewModel.isEqualToHostId(SharedPreferenceManager.userId.toInt())) {
                LiveDialog(
                    activity = this,
                    layoutInflater = layoutInflater,
                    title = "라이브 종료",
                    desc = "라이브를 종료하시겠습니까?\n" +
                        "라이브를 종료하면 대화내용은\n" +
                        "저장되지 않고 사라집니다.\n" +
                        "참여자들 또한 라이브가 종료되어\n" +
                        "강제퇴장 됩니다.",
                    confirmButtonTitle = "예",
                    confirmButtonClick = { finish() },
                    cancelButtonTitle = "아니오",
                    cancelButtonClick = {}
                ).show(screenWidth)
            } else {
                LiveDialog(
                    activity = this,
                    layoutInflater = layoutInflater,
                    title = "라이브 나가기",
                    desc = "라이브에서 나가시겠습니까?",
                    confirmButtonTitle = "예",
                    confirmButtonClick = { finish() },
                    cancelButtonTitle = "아니오",
                    cancelButtonClick = {}
                ).show(screenWidth)
            }
        }
    }

    private fun hideKeyboard(onAfterExecute: () -> Unit) {
        handler.postDelayed({
            imm.hideSoftInputFromWindow(
                window.decorView.applicationWindowToken,
                InputMethodManager.HIDE_NOT_ALWAYS
            )
            onAfterExecute()
        }, 100)
    }

    private fun setupChatAdapter() {
        val rvChat = binding.rvChat
        layoutManager = LinearLayoutManager(
            applicationContext,
            LinearLayoutManager.VERTICAL,
            false
        )
        layoutManager.stackFromEnd = true
        rvChat.layoutManager = layoutManager
        rvChat.addItemDecoration(object : RecyclerView.ItemDecoration() {
            override fun getItemOffsets(
                outRect: Rect,
                view: View,
                parent: RecyclerView,
                state: RecyclerView.State
            ) {
                super.getItemOffsets(outRect, view, parent, state)

                outRect.top = 10f.dpToPx().toInt()
                outRect.bottom = 10f.dpToPx().toInt()
            }
        })
        rvChat.adapter = chatAdapter
        rvChat.setOnScrollChangeListener { _, _, _, _, _ ->
            if (!rvChat.canScrollVertically(1)) {
                binding.tvNewChat.visibility = View.GONE
            }
        }

        binding.tvNewChat.setOnClickListener {
            binding.tvNewChat.visibility = View.GONE
            layoutManager.scrollToPosition(chatAdapter.itemCount - 1)
        }
    }

    private fun setupSpeakerListAdapter() {
        val rvSpeakers = binding.rvSpeakers
        speakerListAdapter = LiveRoomProfileListAdapter()

        rvSpeakers.layoutManager = GridLayoutManager(applicationContext, 5)
        rvSpeakers.addItemDecoration(object : RecyclerView.ItemDecoration() {
            override fun getItemOffsets(
                outRect: Rect,
                view: View,
                parent: RecyclerView,
                state: RecyclerView.State
            ) {
                super.getItemOffsets(outRect, view, parent, state)

                outRect.top = 5f.dpToPx().toInt()
                outRect.bottom = 5f.dpToPx().toInt()
            }
        })
        rvSpeakers.adapter = speakerListAdapter
    }

    private fun inviteSpeaker(peerId: Long) {
        agora.sendRawMessageToPeer(
            receiverUid = peerId.toString(),
            requestType = LiveRoomRequestType.INVITE_SPEAKER
        ) {
            handler.post {
                showDialog(content = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요.")
            }
        }
    }

    private fun setAudience() {
        isSpeaker = false
        isMicrophoneMute = false
        agora.muteLocalAudioStream(false)
        agora.setClientRole(io.agora.rtc2.Constants.CLIENT_ROLE_AUDIENCE)
        handler.postDelayed({
            binding.ivMicrophoneMute.setImageResource(R.drawable.ic_mic_on)
            binding.flMicrophoneMute.visibility = View.GONE
            binding.ivNotiMicrophoneMute.visibility = View.GONE
            speakerListAdapter.muteSpeakers.remove(SharedPreferenceManager.userId.toInt())
        }, 100)
    }

    private fun setBroadcaster() {
        isSpeaker = true
        isMicrophoneMute = false
        agora.muteLocalAudioStream(false)
        agora.setClientRole(io.agora.rtc2.Constants.CLIENT_ROLE_BROADCASTER)
        handler.postDelayed({
            binding.flMicrophoneMute.visibility = View.VISIBLE
            binding.ivNotiMicrophoneMute.visibility = View.GONE
        }, 100)
    }

    private fun changeListenerMessage(peerId: Long, isFromManager: Boolean = false) {
        agora.sendRawMessageToPeer(
            receiverUid = peerId.toString(),
            requestType = LiveRoomRequestType.CHANGE_LISTENER
        ) {
            if (isFromManager) {
                viewModel.getRoomInfo(roomId)
                setManagerMessage()
                releaseManagerMessageToPeer(userId = peerId)

                handler.post {
                    showDialog(
                        content = "${viewModel.getUserNickname(peerId.toInt())}님을 스탭에서 해제했어요."
                    )
                }
            } else {
                handler.post {
                    showDialog(
                        content = "${viewModel.getUserNickname(peerId.toInt())}님을 리스너로 변경했어요."
                    )
                }
            }
        }
    }

    private fun releaseManagerMessageToPeer(userId: Long) {
        agora.sendRawMessageToPeer(
            receiverUid = userId.toString(),
            requestType = LiveRoomRequestType.RELEASE_MANAGER
        ) {}
    }

    private fun setManagerMessageToPeer(userId: Long) {
        agora.sendRawMessageToPeer(
            receiverUid = userId.toString(),
            requestType = LiveRoomRequestType.SET_MANAGER
        ) {}
    }

    private fun setManagerMessage() {
        agora.sendRawMessageToGroup(
            rawMessage = Gson().toJson(
                LiveRoomChatRawMessage(
                    type = LiveRoomChatRawMessageType.SET_MANAGER,
                    message = "",
                    can = 0,
                    donationMessage = ""
                )
            ).toByteArray()
        )
    }

    private fun kickOut(userId: Long) {
        viewModel.kickOut(roomId, userId)
        agora.sendRawMessageToPeer(
            receiverUid = userId.toString(),
            requestType = LiveRoomRequestType.KICK_OUT
        ) {
            handler.post {
                showDialog(
                    content = "${viewModel.getUserNickname(userId.toInt())}님을 내보냈습니다."
                )
            }
        }
    }

    private fun showDialog(
        content: String,
        cancelTitle: String = "",
        cancelAction: (() -> Unit)? = null,
        confirmTitle: String = "",
        confirmAction: (() -> Unit)? = null
    ) {
        roomDialog.setContent(content)

        if (cancelTitle.isNotBlank() && cancelAction != null) {
            roomDialog.setCancel(cancelTitle, cancelAction)
        }

        if (confirmTitle.isNotBlank() && confirmAction != null) {
            roomDialog.setConfirm(confirmTitle, confirmAction)
        }

        roomDialog.show(screenWidth)
    }

    private fun microphoneMute() {
        isMicrophoneMute = !isMicrophoneMute
        agora.muteLocalAudioStream(isMicrophoneMute)

        if (isMicrophoneMute) {
            speakerListAdapter.muteSpeakers.add(SharedPreferenceManager.userId.toInt())
        } else {
            speakerListAdapter.muteSpeakers.remove(SharedPreferenceManager.userId.toInt())
        }
    }

    private fun speakerMute() {
        isSpeakerMute = !isSpeakerMute
        agora.muteAllRemoteAudioStreams(isSpeakerMute)
    }

    private fun inputChat() {
        val nickname = viewModel.getUserNickname(SharedPreferenceManager.userId.toInt())
        val profileUrl = viewModel.getUserProfileUrl(SharedPreferenceManager.userId.toInt())
        val rank = viewModel.getUserRank(SharedPreferenceManager.userId)

        if (binding.etChat.text.isNotBlank() && nickname.isNotBlank() && profileUrl.isNotBlank()) {
            val message = binding.etChat.text.toString()
            chatAdapter.items.add(
                LiveRoomNormalChat(
                    userId = SharedPreferenceManager.userId,
                    profileUrl = profileUrl,
                    nickname = nickname,
                    rank = rank,
                    chat = message
                )
            )
            invalidateChat()

            agora.inputChat(message)
            binding.etChat.setText("")
        }
    }

    @SuppressLint("NotifyDataSetChanged")
    private fun invalidateChat() {
        chatAdapter.notifyDataSetChanged()
        val lastVisiblePosition = layoutManager
            .findLastVisibleItemPosition()
        val itemTotalCount = chatAdapter.itemCount - 1

        if (
            itemTotalCount > 0 &&
            lastVisiblePosition > itemTotalCount - 5
        ) {
            layoutManager
                .scrollToPosition(chatAdapter.itemCount - 1)
            binding.tvNewChat.visibility = View.GONE
        } else {
            binding.tvNewChat.visibility = View.VISIBLE
        }
    }

    private fun donation(can: Int, message: String) {
        val rawMessage = "${can}캔을 후원하셨습니다.\uD83D\uDCB0\uD83E\uDE99"
        val donationRawMessage = Gson().toJson(
            LiveRoomChatRawMessage(
                type = LiveRoomChatRawMessageType.DONATION,
                message = rawMessage,
                can = can,
                donationMessage = message
            )
        )

        viewModel.donation(roomId, can, message) {
            agora.sendRawMessageToGroup(
                rawMessage = donationRawMessage.toByteArray(),
                onSuccess = {
                    handler.post {
                        val nickname =
                            viewModel.getUserNickname(SharedPreferenceManager.userId.toInt())
                        val profileUrl =
                            viewModel.getUserProfileUrl(SharedPreferenceManager.userId.toInt())
                        chatAdapter.items.add(
                            LiveRoomDonationChat(
                                profileUrl,
                                nickname,
                                rawMessage,
                                can,
                                donationMessage = message
                            )
                        )
                        invalidateChat()
                        viewModel.addDonationCan(can)
                    }
                },
                onFailure = {
                    viewModel.refundDonation(roomId)
                }
            )
        }
    }

    private fun joinChannel(roomInfo: GetRoomInfoResponse) {
        val userId = SharedPreferenceManager.userId
        agora.joinRtcChannel(
            uid = userId.toInt(),
            rtcToken = roomInfo.rtcToken,
            channelName = roomInfo.channelName
        )

        agora.createRtmChannelAndLogin(
            uid = userId.toString(),
            rtmToken = roomInfo.rtmToken,
            channelName = roomInfo.channelName,
            rtmChannelListener = object : RtmChannelListener {
                override fun onMemberCountUpdated(i: Int) {
                    Logger.e("onMemberCountUpdated: $i")
                }

                override fun onAttributesUpdated(list: List<RtmChannelAttribute?>?) {}

                @SuppressLint("NotifyDataSetChanged")
                override fun onMessageReceived(message: RtmMessage, fromMember: RtmChannelMember) {
                    Logger.e("onMessageReceived - message: ${message.text}")
                    Logger.e("onMessageReceived - messageType: ${message.messageType}")

                    val nickname = viewModel.getUserNickname(fromMember.userId!!.toInt())
                    val profileUrl = viewModel.getUserProfileUrl(fromMember.userId!!.toInt())
                    val rank = viewModel.getUserRank(fromMember.userId!!.toLong())

                    if (message.messageType == RtmMessageType.RAW) {
                        val rawMessage = Gson().fromJson(
                            String(message.rawMessage),
                            LiveRoomChatRawMessage::class.java
                        )

                        when (rawMessage.type) {
                            LiveRoomChatRawMessageType.EDIT_ROOM_INFO,
                            LiveRoomChatRawMessageType.SET_MANAGER -> {
                                handler.post {
                                    viewModel.getRoomInfo(roomId)
                                }
                            }

                            LiveRoomChatRawMessageType.DONATION -> {
                                handler.post {
                                    chatAdapter.items.add(
                                        LiveRoomDonationChat(
                                            profileUrl,
                                            nickname,
                                            rawMessage.message,
                                            rawMessage.can,
                                            rawMessage.donationMessage ?: ""
                                        )
                                    )
                                    invalidateChat()
                                    viewModel.addDonationCan(rawMessage.can)
                                }
                            }

                            LiveRoomChatRawMessageType.DONATION_STATUS -> {
                                handler.post {
                                    chatAdapter.items.add(
                                        LiveRoomDonationStatusChat(
                                            donationStatusString = rawMessage.message
                                        )
                                    )
                                    chatAdapter.notifyDataSetChanged()
                                    invalidateChat()
                                }
                            }
                        }
                    } else {
                        val chat = message.text

                        if (chat.isNotBlank()) {
                            handler.post {
                                chatAdapter.items.add(
                                    LiveRoomNormalChat(
                                        userId = fromMember.userId.toLong(),
                                        profileUrl = profileUrl,
                                        nickname = nickname,
                                        rank = rank,
                                        chat = chat
                                    )
                                )
                                invalidateChat()
                            }
                        }
                    }
                }

                @SuppressLint("NotifyDataSetChanged")
                override fun onMemberJoined(member: RtmChannelMember) {
                    Logger.e("onMemberJoined: ${member.userId}")
                    viewModel.getRoomInfo(roomId, member.userId.toInt()) {
                        if (it.isNotBlank()) {
                            chatAdapter.items.add(LiveRoomJoinChat(it))
                            invalidateChat()
                        }
                    }
                }

                override fun onMemberLeft(member: RtmChannelMember) {
                    Logger.e("onMemberLeft: ${member.userId}")
                    if (!viewModel.isEqualToHostId(member.userId.toInt())) {
                        viewModel.getRoomInfo(roomId)
                    }
                }
            },
            rtmChannelJoinSuccess = {
                if (userId == roomInfo.managerId) {
                    setBroadcaster()
                } else {
                    setAudience()
                }

                val intent = Intent(this, SodaLiveService::class.java)
                intent.putExtra("roomId", roomId)
                intent.putExtra("content", "라이브 진행중 - ${roomInfo.title}")
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    startForegroundService(intent)
                } else {
                    startService(intent)
                }
            },
            rtmChannelJoinFail = {
                agoraConnectFail()
            }
        )
    }

    private fun agoraConnectFail() {
        showToast("라이브에 접속하지 못했습니다.\n다시 시도해 주세요.")
        finish()
    }

    private val rtcEventHandler = object : IRtcEngineEventHandler() {
        @SuppressLint("NotifyDataSetChanged")
        override fun onAudioVolumeIndication(
            speakers: Array<out AudioVolumeInfo>,
            totalVolume: Int
        ) {
            super.onAudioVolumeIndication(speakers, totalVolume)
            val activeSpeakerIds = speakers
                .asSequence()
                .filter { it.volume > 0 }
                .map { it.uid }
                .toList()

            Logger.e("onAudioVolumeIndication - $activeSpeakerIds")
            handler.post {
                speakerListAdapter.activeSpeakers.clear()
                speakerListAdapter.activeSpeakers.addAll(activeSpeakerIds)

                if (activeSpeakerIds.contains(0) && !isMicrophoneMute) {
                    speakerListAdapter.activeSpeakers.add(SharedPreferenceManager.userId.toInt())
                }
                speakerListAdapter.notifyDataSetChanged()
            }
        }

        override fun onJoinChannelSuccess(channel: String, uid: Int, elapsed: Int) {
            super.onJoinChannelSuccess(channel, uid, elapsed)
            Logger.e("onJoinChannelSuccess - uid: $uid, channel: $channel")
        }

        override fun onActiveSpeaker(uid: Int) {
            Logger.e("onActiveSpeaker - uid: $uid")
            super.onActiveSpeaker(uid)
        }

        override fun onClientRoleChanged(
            oldRole: Int,
            newRole: Int,
            newRoleOptions: ClientRoleOptions?
        ) {
            super.onClientRoleChanged(oldRole, newRole, newRoleOptions)
            Logger.e(
                "onClientRoleChanged - $oldRole - $newRole - " +
                    "${newRoleOptions?.audienceLatencyLevel}"
            )
        }

        @SuppressLint("NotifyDataSetChanged")
        override fun onUserMuteAudio(uid: Int, muted: Boolean) {
            super.onUserMuteAudio(uid, muted)
            handler.post {
                if (muted) {
                    speakerListAdapter.muteSpeakers.add(uid)
                } else {
                    speakerListAdapter.muteSpeakers.remove(uid)
                }
                speakerListAdapter.notifyDataSetChanged()
            }
            Logger.e("onUserMuteAudio - uid: $uid, muted: $muted")
        }

        override fun onUserJoined(uid: Int, elapsed: Int) {
            super.onUserJoined(uid, elapsed)
            Logger.e("onUserJoined - uid: $uid")
            viewModel.getRoomInfo(roomId)
            speakerListAdapter.muteSpeakers.remove(uid)
        }

        override fun onUserOffline(uid: Int, reason: Int) {
            super.onUserOffline(uid, reason)
            Logger.e("onUserOffline - uid: $uid")
            if (viewModel.isEqualToHostId(uid)) {
                handler.post {
                    showToast("라이브가 종료되었습니다.")
                    finish()
                }
            } else {
                viewModel.getRoomInfo(roomId)
                speakerListAdapter.muteSpeakers.remove(uid)
            }
        }
    }

    private val rtmClientListener = object : RtmClientListener {
        override fun onConnectionStateChanged(state: Int, reason: Int) {
            val text =
                "Connection state changed to $state Reason: $reason".trimIndent()

            Logger.e(text)
        }

        override fun onTokenExpired() {}
        override fun onTokenPrivilegeWillExpire() {}

        override fun onPeersOnlineStatusChanged(map: Map<String?, Int?>?) {}
        override fun onMessageReceived(rtmMessage: RtmMessage, peerId: String) {
            Logger.e("text - ${rtmMessage.text}")
            Logger.e("rawMessage - ${String(rtmMessage.rawMessage)}")
            Logger.e("messageType - ${rtmMessage.messageType}")
            if (rtmMessage.messageType == RtmMessageType.RAW) {
                val rawMessage = String(rtmMessage.rawMessage)

                if (rawMessage == LiveRoomRequestType.CHANGE_LISTENER.toString()) {
                    handler.post {
                        viewModel.setListener(
                            roomId,
                            SharedPreferenceManager.userId
                        ) {
                            setAudience()
                            viewModel.getRoomInfo(roomId)

                            if (roomUserProfileDialog.isShowing()) {
                                viewModel.getUserProfile(
                                    roomId = roomId,
                                    userId = peerId.toLong()
                                ) {}
                            }
                        }
                    }

                    return
                }

                if (rawMessage == LiveRoomRequestType.INVITE_SPEAKER.toString() &&
                    !isSpeaker
                ) {
                    handler.post {
                        showDialog(
                            content = "스피커로 초대되었어요",
                            cancelTitle = "다음에요",
                            cancelAction = {},
                            confirmTitle = "스피커로 참여하기",
                            confirmAction = {
                                handler.post {
                                    viewModel.setSpeaker(
                                        roomId,
                                        SharedPreferenceManager.userId
                                    ) {
                                        showDialog(content = "스피커가 되었어요!")
                                        setBroadcaster()
                                        viewModel.getRoomInfo(roomId)
                                    }
                                }
                            }
                        )
                    }

                    return
                }

                if (rawMessage == LiveRoomRequestType.KICK_OUT.toString()) {
                    handler.post {
                        finish()
                        showToast(
                            "${viewModel.getManagerNickname()}님이 라이브에서 내보냈습니다."
                        )
                    }

                    return
                }

                if (rawMessage == LiveRoomRequestType.SET_MANAGER.toString()) {
                    if (isSpeaker) {
                        setAudience()
                    }
                    handler.post {
                        showDialog(
                            content = "${viewModel.getManagerNickname()}님이 스탭으로 지정했습니다."
                        )
                    }
                    return
                }

                if (rawMessage == LiveRoomRequestType.RELEASE_MANAGER.toString()) {
                    handler.post {
                        showDialog(
                            content = "${viewModel.getManagerNickname()}님이 스탭에서 해제했습니다."
                        )
                    }
                    viewModel.getRoomInfo(roomId = roomId)
                    return
                }
            }
        }
    }
}
