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

import android.animation.ObjectAnimator
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.graphics.Typeface
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.CountDownTimer
import android.os.Handler
import android.os.Looper
import android.text.Spannable
import android.text.SpannableString
import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import android.view.LayoutInflater
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.FrameLayout
import android.widget.ImageView
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.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.transform.CircleCropTransformation
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
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 kotlinx.coroutines.launch
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.base.SodaDialog
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.loadUrl
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.chat.LiveRoomRouletteDonationChat
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.donation.LiveRoomDonationResponse
import kr.co.vividnext.sodalive.live.room.info.GetRoomInfoResponse
import kr.co.vividnext.sodalive.live.room.like.LiveRoomHeartRankingDialog
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.live.roulette.RoulettePreviewDialog
import kr.co.vividnext.sodalive.live.roulette.RouletteSpinDialog
import kr.co.vividnext.sodalive.live.roulette.config.RouletteConfigActivity
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
import kotlin.random.Random

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 isHost = false
    private var isNoChatting = false
    private var remainingNoChattingTime = NO_CHATTING_TIME

    private val signatureImageUrlList = mutableListOf<String>()
    private var signatureImageUrl = ""
        set(value) {
            field = value

            if (field.isNotBlank()) {
                showSignatureImage()
            }
        }

    private val signatureList = mutableListOf<LiveRoomDonationResponse>()
    private var signature: LiveRoomDonationResponse? = null
        set(value) {
            field = value

            if (field != null) {
                showSignatureImage()
            }
        }

    private val heartNicknameList = mutableListOf<String>()
    private var heartNickname = ""
        set(value) {
            field = value

            if (field.isNotBlank()) {
                showHeartMessage()
                handler.postDelayed({
                    if (heartNicknameList.isNotEmpty()) {
                        heartNickname = heartNicknameList.removeAt(0)
                    } else {
                        hideHeartMessage()
                    }
                }, 1500)
            }
        }

    private var isShowSignatureImage = false
    private var isAvailableLikeHeart = false
    private var buttonPosition = IntArray(2)

    private val countDownTimer = object : CountDownTimer(remainingNoChattingTime * 1000, 1000) {
        override fun onTick(millisUntilFinished: Long) {
            remainingNoChattingTime -= 1
        }

        override fun onFinish() {
            isNoChatting = false
            remainingNoChattingTime = NO_CHATTING_TIME
            removeNoChatRoom()
            Toast.makeText(
                applicationContext,
                "채팅금지가 해제되었습니다.",
                Toast.LENGTH_SHORT
            ).show()
        }
    }

    private fun startNoChatting() {
        hideKeyboard {
            binding.etChat.clearFocus()

            isNoChatting = true
            Toast.makeText(
                applicationContext,
                "${viewModel.getManagerNickname()}님이 3분간 채팅을 금지하였습니다.",
                Toast.LENGTH_SHORT
            ).show()
            countDownTimer.start()
        }
    }

    private fun addNoChatRoom() {
        val noChatRoomList = SharedPreferenceManager.noChatRoomList.toMutableList()
        noChatRoomList.add(roomId)
        SharedPreferenceManager.noChatRoomList = noChatRoomList
    }

    private fun removeNoChatRoom() {
        val noChatRoomList = SharedPreferenceManager.noChatRoomList.toMutableList()
        noChatRoomList.remove(roomId)
        SharedPreferenceManager.noChatRoomList = noChatRoomList
    }

    private fun containNoChatRoom(): Boolean {
        val noChatRoomList = SharedPreferenceManager.noChatRoomList
        return noChatRoomList.contains(roomId)
    }

    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()
            }
        }

    private val rouletteConfigResult = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        val resultCode = result.resultCode
        val isActiveRoulette = result.data?.getBooleanExtra(Constants.EXTRA_RESULT_ROULETTE, false)

        if (resultCode == RESULT_OK && isActiveRoulette != null) {
            agora.sendRawMessageToGroup(
                rawMessage = Gson().toJson(
                    LiveRoomChatRawMessage(
                        type = LiveRoomChatRawMessageType.TOGGLE_ROULETTE,
                        message = "",
                        can = 0,
                        donationMessage = "",
                        isActiveRoulette = isActiveRoulette
                    )
                ).toByteArray()
            )
        }
    }

    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)
        viewModel.getBlockedMemberIdList()
    }

    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 <= 5) {
                    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)
            },
            onClickNoChatting = { userId, nickname, profileUrl ->
                LiveRoomNoChattingDialog(
                    activity = this,
                    layoutInflater = layoutInflater,
                    nickname = nickname,
                    profileUrl = profileUrl,
                    confirmButtonClick = { setNoChatting(userId, nickname) }
                ).show(screenWidth)
            },
            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())
            },
            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 <= 5) {
                    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)
            },
            onClickNoChatting = { userId, nickname, profileUrl ->
                LiveRoomNoChattingDialog(
                    activity = this,
                    layoutInflater = layoutInflater,
                    nickname = nickname,
                    profileUrl = profileUrl,
                    confirmButtonClick = { setNoChatting(userId, nickname) }
                ).show(screenWidth)
            },
            onClickPopupMenu = { userId, nickname, isBlock, view ->
                showOptionMenu(
                    this,
                    userId,
                    nickname,
                    isBlock,
                    view,
                )
            }
        )

        binding.etChat.setOnFocusChangeListener { view, hasFocus ->
            if (isNoChatting && hasFocus) {
                Toast.makeText(
                    applicationContext,
                    "${remainingNoChattingTime}초 동안 채팅하실 수 없습니다",
                    Toast.LENGTH_SHORT
                ).show()
                view.clearFocus()
            }
        }
        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.etChat.setOnEditorActionListener { _, actionId, _ ->
            if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_SEND) {
                inputChat()
                true
            } else {
                false
            }
        }
        binding.ivSend.setOnClickListener { inputChat() }
        binding.flDonation.setOnClickListener {
            hideKeyboard {
                val dialog = LiveRoomDonationDialog(
                    this,
                    LayoutInflater.from(this),
                    isLiveDonation = true
                ) { can, message, isSecret ->
                    if (isSecret && can < 10) {
                        showToast("비밀 미션은 최소 10캔 이상부터 이용이 가능합니다.")
                    } else if (can < 1) {
                        showToast("1캔 이상 후원하실 수 있습니다.")
                    } else {
                        donation(can, message, isSecret)
                    }
                }

                dialog.show(screenWidth)
            }
        }
        binding.tvNotification.setOnClickListener { viewModel.toggleShowNotice() }
        binding.tvMenuPan.setOnClickListener { viewModel.toggleShowMenuPan() }

        binding.tvBgSwitch.setOnClickListener { viewModel.toggleBackgroundImage() }
        binding.tvSignatureSwitch.setOnClickListener { viewModel.toggleSignatureImage() }
        binding.llDonation.setOnClickListener {
            LiveRoomDonationRankingDialog(
                activity = this,
                layoutInflater = layoutInflater,
                viewModel = viewModel,
                roomId = roomId
            ).show()
        }
        binding.llHeart.setOnClickListener {
            LiveRoomHeartRankingDialog(
                activity = this,
                layoutInflater = layoutInflater,
                getHeartRanking = { viewModel.heartStatus(roomId, it) }
            ).show()
        }

        setupChatAdapter()
        setupSpeakerListAdapter()
    }

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

    private fun setHeartButtonPosition() {
        handler.postDelayed(
            {
                // 버튼의 위치
                val button = if (isHost) {
                    binding.flRouletteSettings
                } else {
                    binding.flLikeHeart
                }
                buttonPosition = IntArray(2)
                button.getLocationInWindow(buttonPosition)
            },
            500
        )
    }

    private fun secondToMillis(second: Float): Long {
        return (second * 1000).toLong()
    }

    private fun addHeartAnimation() {
        val button = if (isHost) {
            binding.flRouletteSettings
        } else {
            binding.flLikeHeart
        }

        // 하트 이미지뷰 생성
        val heart = ImageView(this).apply {
            setImageResource(R.drawable.ic_heart_pink) // 투명한 하트 이미지
            layoutParams = FrameLayout.LayoutParams(
                33.3f.dpToPx().toInt(),
                33.3f.dpToPx().toInt()
            ) // 하트 크기 설정
        }

        // 하트의 초기 위치를 버튼의 위치로 설정
        heart.x = (buttonPosition[0] + button.width / 2f - 50) // X축 (가운데 정렬)
        heart.y = (buttonPosition[1] - 100).toFloat() // Y축 (버튼 바로 위에 생성)

        binding.flRoot.addView(heart)

        // 하트가 위로 올라가는 애니메이션
        val animateDuration = secondToMillis(2.5f)
        val moveUpAnimator = ObjectAnimator.ofFloat(
            heart,
            "translationY",
            heart.y,
            heart.y - (screenHeight * 1000 / 2337)
        ).apply {
            duration = animateDuration
            interpolator = AccelerateDecelerateInterpolator()
        }

        val isNegativeFirst = Random.nextBoolean()

        // 좌우 이동 범위를 랜덤으로 설정
        val x = 13.3f
        val startX = if (isNegativeFirst) (0 - x).dpToPx() else x.dpToPx()
        val endX = if (isNegativeFirst) x.dpToPx() else (0 - x).dpToPx()

        val moveAnimator = ObjectAnimator.ofFloat(
            heart,
            "translationX",
            heart.x + startX,
            heart.x + endX
        ).apply {
            duration = secondToMillis(1.5f)
            repeatCount = ObjectAnimator.INFINITE
            repeatMode = ObjectAnimator.REVERSE
            interpolator = AccelerateDecelerateInterpolator()
        }

        val scaleXAnimator = ObjectAnimator.ofFloat(
            heart,
            "scaleX",
            0.5f,
            1.0f
        ).apply {
            duration = animateDuration
        }

        val scaleYAnimator = ObjectAnimator.ofFloat(
            heart,
            "scaleY",
            0.5f,
            1.0f
        ).apply {
            duration = animateDuration
        }

        // 하트 투명도 애니메이션
        val fadeOutAnimator = ObjectAnimator.ofFloat(
            heart,
            "alpha",
            1f,
            0f
        ).apply {
            duration = animateDuration
        }

        // 애니메이션 실행
        moveUpAnimator.start()
        moveAnimator.start()
        fadeOutAnimator.start()
        scaleXAnimator.start()
        scaleYAnimator.start()

        // 애니메이션이 끝나면 하트 제거
        handler.postDelayed({
            binding.flRoot.removeView(heart)
        }, animateDuration)
    }

    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)
                        viewModel.removeBlockedMember(memberId = 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 message = if (SharedPreferenceManager.role == MemberRole.CREATOR.name) {
            """
                ${nickname}님을 차단하시겠습니까?

                사용자를 차단하면 사용자는 아래 기능이 제한됩니다.
                - 내가 개설한 라이브 입장 불가
                - 나에게 메시지 보내기 불가
                - 내 채널의 팬Talk 작성불가
            """.trimIndent()
        } else {
            """
                ${nickname}님을 차단하시겠습니까?

                - 사용자를 차단하면 '차단한 사용자의 라이브 중 채팅'이 보이지 않습니다.
            """.trimIndent()
        }
        val dialog = AlertDialog.Builder(this)
        dialog.setTitle("사용자 차단")
        dialog.setMessage(message)
        dialog.setPositiveButton("차단") { _, _ ->
            roomUserProfileDialog.dismiss()
            viewModel.memberBlock(userId) {
                viewModel.addBlockedMember(memberId = userId)
                if (viewModel.roomInfoResponse.creatorId == SharedPreferenceManager.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_3bb9f1
                    )
                )
                binding.tvBgSwitch
                    .setBackgroundResource(R.drawable.bg_round_corner_5_3_transparent_3bb9f1)
            } 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_5_3_transparent_bbbbbb)
            }
        }

        viewModel.isSignatureOn.observe(this) {
            if (it) {
                binding.tvSignatureSwitch.text = "시그 ON"
                binding.tvSignatureSwitch.setTextColor(
                    ContextCompat.getColor(
                        applicationContext,
                        R.color.color_3bb9f1
                    )
                )
                binding.tvSignatureSwitch
                    .setBackgroundResource(R.drawable.bg_round_corner_5_3_transparent_3bb9f1)
            } else {
                binding.tvSignatureSwitch.text = "시그 OFF"
                binding.tvSignatureSwitch.setTextColor(
                    ContextCompat.getColor(
                        applicationContext,
                        R.color.color_eeeeee
                    )
                )
                binding.tvSignatureSwitch
                    .setBackgroundResource(R.drawable.bg_round_corner_5_3_transparent_bbbbbb)
                binding.ivSignature.visibility = View.GONE
            }
        }

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

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

        viewModel.changeIsAdultLiveData.observe(this) {
            if (it && !SharedPreferenceManager.isAuth) {
                agora.muteAllRemoteAudioStreams(true)
                binding.rvChat.visibility = View.INVISIBLE
                SodaDialog(this@LiveRoomActivity,
                    layoutInflater,
                    "알림",
                    "지금 참여하던 라이브는 '19세 이상' 연령제한이 설정되어 정보통신망 이용촉진 및 정보 보호 등에 관한 법률 및 청소년 보호법의 규정에 의해 만 19세 미만의 청소년은 이용할 수 없습니다.\n마이페이지에서 본인인증 후 다시 이용하시기 바랍니다.",
                    "확인",
                    { finish() }
                ).show(screenWidth)
            }
        }

        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.tv19.visibility = if (response.isAdult) {
                View.VISIBLE
            } else {
                View.GONE
            }

            binding.tvTitle.text = response.title

            binding.flDonation.visibility =
                if (response.creatorId != SharedPreferenceManager.userId) {
                    View.VISIBLE
                } else {
                    View.GONE
                }

            binding.flDonationMessageList.setOnClickListener {
                hideKeyboard {
                    LiveRoomDonationMessageDialog(
                        layoutInflater = LayoutInflater.from(this),
                        creatorId = response.creatorId,
                        activity = this,
                        donationMessageListLiveData = donationMessageViewModel
                            .donationMessageListLiveData,
                        donationMessageCountLiveData = donationMessageViewModel
                            .donationMessageCountLiveData,
                        getDonationMessageList = {
                            donationMessageViewModel.getDonationMessageList(roomId = roomId)
                        },
                        deleteDonationMessage = {
                            if (
                                response.creatorId == SharedPreferenceManager.userId &&
                                SharedPreferenceManager.role == MemberRole.CREATOR.name
                            ) {
                                donationMessageViewModel.deleteDonationMessage(
                                    roomId = roomId,
                                    uuid = it
                                )
                            }
                        },
                        copyMessage = {
                            val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
                            clipboard.setPrimaryClip(ClipData.newPlainText(it, it))
                            showToast("후원 히스토리가 복사되었습니다.")
                        }
                    ).show()
                }
            }

            speakerListAdapter.managerId = response.creatorId
            speakerListAdapter.updateList(
                response.speakerList.filter {
                    it.id != response.creatorId
                }
            )

            if (response.creatorId == SharedPreferenceManager.userId) {
                binding.ivEdit.setOnClickListener {
                    viewModel.getAllMenuPreset {
                        roomInfoEditDialog.setRoomInfo(
                            response.title,
                            response.notice,
                            response.isAdult
                        )
                        roomInfoEditDialog.setCoverImageUrl(response.coverImageUrl)
                        roomInfoEditDialog.setMenuPreset(it)
                        roomInfoEditDialog.setConfirmAction { newTitle, newContent, newCoverImageUri, isActivateMenu, menuId, menu, isAdult ->
                            viewModel.editLiveRoomInfo(
                                response.roomId,
                                newTitle,
                                newContent,
                                newCoverImageUri,
                                isActivateMenu,
                                menuId,
                                menu,
                                isAdult,
                                onSuccess = {
                                    Toast.makeText(
                                        applicationContext,
                                        "라이브 정보가 수정되었습니다.",
                                        Toast.LENGTH_LONG
                                    ).show()

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

                        handler.post {
                            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)
                }
            }

            if (response.creatorId == SharedPreferenceManager.userId) {
                binding.llViewUsers.visibility = View.VISIBLE
                binding.llViewUsers.setOnClickListener { roomProfileDialog.show() }
            } else {
                binding.llViewUsers.visibility = View.GONE
            }

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

            binding.tvCreatorNickname.text = response.creatorNickname
            binding.ivCreatorProfile.loadUrl(response.creatorProfileUrl) {
                crossfade(true)
                placeholder(R.drawable.ic_place_holder)
                transformations(CircleCropTransformation())
            }

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

            if (response.creatorId != SharedPreferenceManager.userId) {
                binding.ivCreatorFollow.visibility = View.VISIBLE
                if (response.isFollowing) {
                    binding.ivCreatorFollow.setImageResource(R.drawable.btn_following)
                    binding.ivCreatorFollow.setOnClickListener {
                        viewModel.creatorUnFollow(
                            creatorId = response.creatorId,
                            roomId = roomId
                        )
                    }
                } else {
                    binding.ivCreatorFollow.setImageResource(R.drawable.btn_follow)
                    binding.ivCreatorFollow.setOnClickListener {
                        viewModel.creatorFollow(
                            creatorId = response.creatorId,
                            roomId = roomId
                        )
                    }
                }
            } else {
                binding.ivCreatorFollow.visibility = View.GONE
            }

            isHost = response.creatorId == SharedPreferenceManager.userId
            initLikeHeartButton()
            initRouletteSettingButton()
            activatingRouletteButton(isActiveRoulette = response.isActiveRoulette)

            if (response.menuPan.isNotBlank()) {
                binding.tvMenuPan.visibility = View.VISIBLE
                binding.tvMenuPanDetail.text = response.menuPan
            } else {
                viewModel.toggleShowMenuPan(false)
                binding.tvMenuPan.visibility = View.GONE
                binding.tvMenuPanDetail.text = ""
            }

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

        viewModel.isShowNotice.observe(this) {
            if (it) {
                binding.tvNotification.setTextColor(
                    ContextCompat.getColor(
                        applicationContext,
                        R.color.color_3bb9f1
                    )
                )
                binding.tvNotification.setBackgroundResource(
                    R.drawable.bg_round_corner_5_3_transparent_3bb9f1
                )

                binding.llNotice.visibility = View.VISIBLE
            } else {
                binding.tvNotification.setTextColor(
                    ContextCompat.getColor(
                        applicationContext,
                        R.color.color_bbbbbb
                    )
                )
                binding.tvNotification.setBackgroundResource(
                    R.drawable.bg_round_corner_5_3_transparent_bbbbbb
                )

                binding.llNotice.visibility = View.GONE
            }
        }

        viewModel.isShowMenuPan.observe(this) {
            if (it) {
                binding.tvMenuPan.setTextColor(
                    ContextCompat.getColor(
                        applicationContext,
                        R.color.color_3bb9f1
                    )
                )
                binding.tvMenuPan.setBackgroundResource(
                    R.drawable.bg_round_corner_5_3_transparent_3bb9f1
                )

                binding.llMenuPan.visibility = View.VISIBLE
            } else {
                binding.tvMenuPan.setTextColor(
                    ContextCompat.getColor(
                        applicationContext,
                        R.color.color_bbbbbb
                    )
                )
                binding.tvMenuPan.setBackgroundResource(
                    R.drawable.bg_round_corner_5_3_transparent_bbbbbb
                )

                binding.llMenuPan.visibility = View.GONE
            }
        }

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

        viewModel.totalHeartCount.observe(this) {
            binding.tvTotalHeart.text = it.moneyFormat()
        }

        viewModel.coverImageUrlLiveData.observe(this) {
            binding.ivCover.loadUrl(it)
        }
    }

    private fun initLikeHeartButton() {
        if (!isHost) {
            binding.flLikeHeart.visibility = View.VISIBLE
            binding.flLikeHeart.setOnClickListener {
                if (isAvailableLikeHeart) {
                    binding.flLikeHeart.isEnabled = false
                    viewModel.likeHeart(
                        roomId = roomId,
                        onSuccess = {
                            val donationRawMessage = Gson().toJson(
                                LiveRoomChatRawMessage(
                                    type = LiveRoomChatRawMessageType.HEART_DONATION,
                                    message = "",
                                    can = 1,
                                    signature = null,
                                    signatureImageUrl = null,
                                    donationMessage = null
                                )
                            )

                            agora.sendRawMessageToGroup(
                                rawMessage = donationRawMessage.toByteArray(),
                                onSuccess = {
                                    val nickname = viewModel.getUserNickname(
                                        SharedPreferenceManager.userId.toInt()
                                    )
                                    handler.post {
                                        addHeartMessage(nickname)
                                        addHeartAnimation()
                                        lifecycleScope.launch { viewModel.addHeartDonation() }
                                    }
                                },
                                onFailure = {
                                    viewModel.refundDonation(roomId)
                                }
                            )

                            binding.flLikeHeart.isEnabled = true
                        },
                        onFailure = {
                            binding.flLikeHeart.isEnabled = true
                        }
                    )
                } else {
                    SodaDialog(
                        activity = this@LiveRoomActivity,
                        layoutInflater = layoutInflater,
                        title = "안내",
                        desc = "'좋아해요'는 유료 후원입니다.\n" +
                            "클릭시 1캔이 소진됩니다.",
                        confirmButtonTitle = "확인",
                        confirmButtonClick = { isAvailableLikeHeart = true }
                    ).show(screenWidth)
                }
            }
        } else {
            binding.flLikeHeart.visibility = View.GONE
        }
    }

    private fun initRouletteSettingButton() {
        if (isHost) {
            binding.flRouletteSettings.visibility = View.VISIBLE
            binding.flRouletteSettings.setOnClickListener {
                hideKeyboard {
                    rouletteConfigResult.launch(
                        Intent(
                            applicationContext,
                            RouletteConfigActivity::class.java
                        )
                    )
                }
            }
        } else {
            binding.flRouletteSettings.visibility = View.GONE
        }
    }

    private fun activatingRouletteButton(isActiveRoulette: Boolean) {
        handler.postDelayed(
            {
                if (!isHost && isActiveRoulette) {
                    binding.flRoulette.visibility = View.VISIBLE
                    binding.flRoulette.setOnClickListener {
                        hideKeyboard {
                            viewModel.showRoulette {
                                RoulettePreviewDialog(
                                    activity = this,
                                    previewList = it,
                                    onClickSpin = { rouletteId ->
                                        spinRoulette(rouletteId = rouletteId)
                                    },
                                    layoutInflater = layoutInflater
                                ).show()
                            }
                        }
                    }

                } else {
                    binding.flRoulette.visibility = View.GONE
                }
            },
            500
        )
    }

    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 = LinearLayoutManager(
            applicationContext,
            LinearLayoutManager.HORIZONTAL,
            false
        )
        rvSpeakers.addItemDecoration(object : RecyclerView.ItemDecoration() {
            override fun getItemOffsets(
                outRect: Rect,
                view: View,
                parent: RecyclerView,
                state: RecyclerView.State
            ) {
                super.getItemOffsets(outRect, view, parent, state)

                outRect.left = 2f.dpToPx().toInt()
                outRect.right = 2f.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.tvChangeListener.visibility = View.GONE
            binding.tvChangeListener.setOnClickListener { }
            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 setNoChatting(userId: Long, nickname: String) {
        agora.sendRawMessageToPeer(
            receiverUid = userId.toString(),
            requestType = LiveRoomRequestType.NO_CHATTING
        ) {
            handler.post {
                showDialog(content = "${nickname}님을 3분간 채팅금지를 하였습니다.")
            }
        }
    }

    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 (SharedPreferenceManager.userId == viewModel.roomInfoResponse.creatorId) {
            setMuteSpeakerCreator(isMicrophoneMute)
        } else {
            if (isMicrophoneMute) {
                speakerListAdapter.muteSpeakers.add(SharedPreferenceManager.userId.toInt())
            } else {
                speakerListAdapter.muteSpeakers.remove(SharedPreferenceManager.userId.toInt())
            }
        }
    }

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

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

        if (isNoChatting) {
            Toast.makeText(
                applicationContext,
                "${remainingNoChattingTime}초 동안 채팅하실 수 없습니다",
                Toast.LENGTH_SHORT
            ).show()
        } else 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, isSecret: Boolean) {
        val rawMessage = if (isSecret) {
            "${can}캔으로 비밀미션을 보냈습니다.\uD83E\uDD2B"
        } else {
            "${can}캔을 후원하셨습니다.\uD83D\uDCB0\uD83E\uDE99"
        }

        viewModel.donation(roomId, can, message, isSecret) { signature ->
            val donationRawMessage = Gson().toJson(
                LiveRoomChatRawMessage(
                    type = if (isSecret) {
                        LiveRoomChatRawMessageType.SECRET_DONATION
                    } else {
                        LiveRoomChatRawMessageType.DONATION
                    },
                    message = rawMessage,
                    can = can,
                    signature = signature,
                    signatureImageUrl = signature?.imageUrl,
                    donationMessage = message
                )
            )

            if (isSecret) {
                agora.sendRawMessageToPeer(
                    receiverUid = viewModel.roomInfoResponse.creatorId.toString(),
                    rawMessage = donationRawMessage.toByteArray()
                ) {
                    handler.post {
                        val nickname =
                            viewModel.getUserNickname(SharedPreferenceManager.userId.toInt())
                        val profileUrl =
                            viewModel.getUserProfileUrl(SharedPreferenceManager.userId.toInt())
                        chatAdapter.items.add(
                            LiveRoomDonationChat(
                                memberId = SharedPreferenceManager.userId,
                                profileUrl,
                                nickname,
                                rawMessage,
                                can,
                                donationMessage = message
                            )
                        )
                        invalidateChat()
                        addSignature(signature)
                    }
                }
            } else {
                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(
                                    memberId = SharedPreferenceManager.userId,
                                    profileUrl,
                                    nickname,
                                    rawMessage,
                                    can,
                                    donationMessage = message
                                )
                            )
                            invalidateChat()
                            lifecycleScope.launch { viewModel.addDonationCan(can) }
                            addSignature(signature)
                        }
                    },
                    onFailure = {
                        viewModel.refundDonation(roomId)
                    }
                )
            }
        }
    }

    private fun spinRoulette(rouletteId: Long) {
        viewModel.spinRoulette(roomId = roomId, rouletteId = rouletteId) { can, items, randomItem ->
            val rouletteRawMessage = Gson().toJson(
                LiveRoomChatRawMessage(
                    type = LiveRoomChatRawMessageType.ROULETTE_DONATION,
                    message = randomItem,
                    can = can,
                    donationMessage = "",
                )
            )

            RouletteSpinDialog(
                activity = this@LiveRoomActivity,
                items = items,
                selectedItem = randomItem,
                layoutInflater = layoutInflater
            ) {
                agora.sendRawMessageToGroup(
                    rawMessage = rouletteRawMessage.toByteArray(),
                    onSuccess = {
                        handler.post {
                            chatAdapter.items.add(
                                LiveRoomRouletteDonationChat(
                                    profileUrl = SharedPreferenceManager.profileImage,
                                    nickname = SharedPreferenceManager.nickname,
                                    rouletteResult = randomItem
                                )
                            )
                            invalidateChat()
                            lifecycleScope.launch { viewModel.addDonationCan(can) }
                        }
                    },
                    onFailure = {
                        viewModel.refundRouletteDonation(roomId)
                    }
                )
            }.show()
        }
    }

    private fun joinChannel(roomInfo: GetRoomInfoResponse) {
        loadingDialog.show(width = screenWidth, message = "라이브에 입장하고 있습니다.")

        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(
                                            memberId = fromMember.userId.toLong(),
                                            profileUrl,
                                            nickname,
                                            rawMessage.message,
                                            rawMessage.can,
                                            rawMessage.donationMessage ?: ""
                                        )
                                    )
                                    invalidateChat()
                                    lifecycleScope.launch {
                                        viewModel.addDonationCan(rawMessage.can)
                                    }

                                    if (rawMessage.signature != null) {
                                        addSignature(rawMessage.signature)
                                    } else if (rawMessage.signatureImageUrl != null) {
                                        addSignatureImage(rawMessage.signatureImageUrl)
                                    }
                                }
                            }

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

                            LiveRoomChatRawMessageType.TOGGLE_ROULETTE -> {
                                activatingRouletteButton(
                                    isActiveRoulette = rawMessage.isActiveRoulette ?: false
                                )
                            }

                            LiveRoomChatRawMessageType.ROULETTE_DONATION -> {
                                handler.post {
                                    chatAdapter.items.add(
                                        LiveRoomRouletteDonationChat(
                                            profileUrl = profileUrl,
                                            nickname = nickname,
                                            rouletteResult = rawMessage.message
                                        )
                                    )
                                    invalidateChat()
                                    lifecycleScope.launch {
                                        viewModel.addDonationCan(rawMessage.can)
                                    }
                                }
                            }

                            LiveRoomChatRawMessageType.HEART_DONATION -> {
                                handler.post {
                                    addHeartMessage(nickname)
                                    addHeartAnimation()
                                    lifecycleScope.launch { viewModel.addHeartDonation() }
                                }
                            }

                            else -> {}
                        }
                    } else {
                        val memberId = fromMember.userId.toLong()
                        if (viewModel.isNotBlockedMember(memberId)) {
                            val chat = message.text

                            if (chat.isNotBlank()) {
                                handler.post {
                                    chatAdapter.items.add(
                                        LiveRoomNormalChat(
                                            userId = memberId,
                                            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 = {
                handler.post {
                    loadingDialog.dismiss()
                }

                if (userId == roomInfo.creatorId) {
                    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)
                }

                if (containNoChatRoom()) {
                    startNoChatting()
                }
                setHeartButtonPosition()
            },
            rtmChannelJoinFail = {
                agoraConnectFail()
            }
        )
    }

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

    private fun setMuteSpeakerCreator(isMute: Boolean) {
        binding.ivMute.visibility = if (isMute) {
            View.VISIBLE
        } else {
            View.GONE
        }
    }

    private val rtcEventHandler = object : IRtcEngineEventHandler() {
        @SuppressLint("NotifyDataSetChanged")
        override fun onAudioVolumeIndication(
            speakers: Array<out AudioVolumeInfo>,
            totalVolume: Int
        ) {
            super.onAudioVolumeIndication(speakers, totalVolume)

            Logger.e("onAudioVolumeIndication - $speakers")

            val activeSpeakerIds = speakers
                .filter { it.volume > 0 }
                .map { it.uid }

            Logger.e("onAudioVolumeIndication - $activeSpeakerIds")
            handler.post {
                if (!activeSpeakerIds.contains(0)) {
                    speakerListAdapter.activeSpeakers.clear()
                    speakerListAdapter.activeSpeakers.addAll(activeSpeakerIds)
                    speakerListAdapter.notifyDataSetChanged()

                    if (activeSpeakerIds.contains(viewModel.roomInfoResponse.creatorId.toInt())) {
                        binding.ivCreatorProfileBg.visibility = View.VISIBLE
                    } else {
                        binding.ivCreatorProfileBg.visibility = View.GONE
                    }
                }
            }
        }

        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 (uid == viewModel.roomInfoResponse.creatorId.toInt()) {
                    setMuteSpeakerCreator(muted)
                } else {
                    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)
                                        binding.tvChangeListener.visibility = View.VISIBLE
                                        binding.tvChangeListener.setOnClickListener {
                                            handler.post {
                                                viewModel.setListener(
                                                    roomId,
                                                    SharedPreferenceManager.userId
                                                ) {
                                                    setAudience()
                                                    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
                }

                if (rawMessage == LiveRoomRequestType.NO_CHATTING.toString() && !isNoChatting) {
                    handler.post {
                        addNoChatRoom()
                        startNoChatting()
                    }
                    return
                }

                val message = Gson().fromJson(
                    rawMessage,
                    LiveRoomChatRawMessage::class.java
                )

                if (message.type == LiveRoomChatRawMessageType.SECRET_DONATION) {
                    val nickname = viewModel.getUserNickname(peerId.toInt())
                    val profileUrl = viewModel.getUserProfileUrl(peerId.toInt())

                    handler.post {
                        chatAdapter.items.add(
                            LiveRoomDonationChat(
                                memberId = peerId.toLong(),
                                profileUrl,
                                nickname,
                                message.message,
                                message.can,
                                message.donationMessage ?: ""
                            )
                        )
                        invalidateChat()

                        if (message.signature != null) {
                            addSignature(message.signature)
                        } else if (message.signatureImageUrl != null) {
                            addSignatureImage(message.signatureImageUrl)
                        }
                    }
                }
            }
        }
    }

    private fun addHeartMessage(nickname: String) {
        if (heartNickname.isBlank()) {
            heartNickname = nickname
        } else {
            heartNicknameList.add(nickname)
        }
    }

    private fun showHeartMessage() {
        val str = "'${heartNickname}'님이 마음을 전했습니다 : \uD83D\uDC95"
        val spStr = SpannableString(str)

        spStr.setSpan(
            ForegroundColorSpan(
                ContextCompat.getColor(
                    applicationContext,
                    R.color.color_ec3aa6
                )
            ),
            str.indexOf("'") + 1,
            str.indexOf("'님"),
            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
        )

        spStr.setSpan(
            StyleSpan(Typeface.BOLD),
            str.indexOf("'"),
            str.indexOf("'님"),
            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
        )
        binding.tvHeartMessage.text = spStr
        binding.tvHeartMessage.visibility = View.VISIBLE
    }

    private fun hideHeartMessage() {
        heartNickname = ""
        binding.tvHeartMessage.text = ""
        binding.tvHeartMessage.visibility = View.GONE
    }

    private fun addSignatureImage(imageUrl: String) {
        if (imageUrl.isNotBlank()) {
            if (!isShowSignatureImage) {
                isShowSignatureImage = true
                signatureImageUrl = imageUrl
            } else {
                signatureImageUrlList.add(imageUrl)
            }
        }
    }

    private fun addSignature(signature: LiveRoomDonationResponse?) {
        if (signature != null) {
            if (!isShowSignatureImage) {
                this.signature = signature
                isShowSignatureImage = true
            } else {
                signatureList.add(signature)
            }
        }
    }

    private fun showSignatureImage() {
        if (signature != null) {
            Glide
                .with(this)
                .load(signature!!.imageUrl)
                .listener(object : RequestListener<Drawable> {
                    override fun onLoadFailed(
                        e: GlideException?,
                        model: Any?,
                        target: Target<Drawable>?,
                        isFirstResource: Boolean
                    ): Boolean {
                        return false
                    }

                    override fun onResourceReady(
                        resource: Drawable?,
                        model: Any?,
                        target: Target<Drawable>?,
                        dataSource: DataSource?,
                        isFirstResource: Boolean
                    ): Boolean {
                        if (viewModel.isSignatureOn.value!!) {
                            binding.ivSignature.visibility = View.VISIBLE
                        }

                        handler.postDelayed({
                            if (signatureList.isNotEmpty()) {
                                signature = signatureList.removeAt(0)
                            } else {
                                signature = null
                                isShowSignatureImage = false
                                binding.ivSignature.setImageDrawable(null)
                                binding.ivSignature.visibility = View.GONE
                            }
                        }, signature!!.time * 1000L)

                        return false
                    }
                })
                .into(binding.ivSignature)
        } else if (signatureImageUrl.isNotBlank()) {
            Glide
                .with(this)
                .load(signatureImageUrl)
                .listener(object : RequestListener<Drawable> {
                    override fun onLoadFailed(
                        e: GlideException?,
                        model: Any?,
                        target: Target<Drawable>?,
                        isFirstResource: Boolean
                    ): Boolean {
                        return false
                    }

                    override fun onResourceReady(
                        resource: Drawable?,
                        model: Any?,
                        target: Target<Drawable>?,
                        dataSource: DataSource?,
                        isFirstResource: Boolean
                    ): Boolean {
                        if (viewModel.isSignatureOn.value!!) {
                            binding.ivSignature.visibility = View.VISIBLE
                        }

                        handler.postDelayed({
                            if (signatureImageUrlList.isNotEmpty()) {
                                signatureImageUrl = signatureImageUrlList.removeAt(0)
                            } else {
                                signatureImageUrl = ""
                                isShowSignatureImage = false
                                binding.ivSignature.setImageDrawable(null)
                                binding.ivSignature.visibility = View.GONE
                            }
                        }, 7000)

                        return false
                    }

                })
                .into(binding.ivSignature)
        }
    }

    companion object {
        private const val NO_CHATTING_TIME = 180L
    }
}
