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

import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.google.firebase.dynamiclinks.ShortDynamicLink
import com.google.firebase.dynamiclinks.ktx.androidParameters
import com.google.firebase.dynamiclinks.ktx.dynamicLinks
import com.google.firebase.dynamiclinks.ktx.iosParameters
import com.google.firebase.dynamiclinks.ktx.shortLinkAsync
import com.google.firebase.ktx.Firebase
import com.google.gson.Gson
import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.live.LiveRepository
import kr.co.vividnext.sodalive.live.room.donation.GetLiveRoomDonationStatusResponse
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.GetLiveRoomHeartListResponse
import kr.co.vividnext.sodalive.live.room.menu.GetMenuPresetResponse
import kr.co.vividnext.sodalive.live.room.profile.GetLiveRoomUserProfileResponse
import kr.co.vividnext.sodalive.live.room.update.EditLiveRoomInfoRequest
import kr.co.vividnext.sodalive.live.roulette.RouletteItem
import kr.co.vividnext.sodalive.live.roulette.RoulettePreview
import kr.co.vividnext.sodalive.live.roulette.RoulettePreviewItem
import kr.co.vividnext.sodalive.live.roulette.RouletteRepository
import kr.co.vividnext.sodalive.live.roulette.SpinRouletteRequest
import kr.co.vividnext.sodalive.report.ReportRepository
import kr.co.vividnext.sodalive.report.ReportRequest
import kr.co.vividnext.sodalive.report.ReportType
import kr.co.vividnext.sodalive.user.UserRepository
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import java.util.Locale

class LiveRoomViewModel(
    private val repository: LiveRepository,
    private val userRepository: UserRepository,
    private val reportRepository: ReportRepository,
    private val rouletteRepository: RouletteRepository
) : BaseViewModel() {
    private val _roomInfoLiveData = MutableLiveData<GetRoomInfoResponse>()
    val roomInfoLiveData: LiveData<GetRoomInfoResponse>
        get() = _roomInfoLiveData

    private val _toastLiveData = MutableLiveData<String?>()
    val toastLiveData: LiveData<String?>
        get() = _toastLiveData

    private val _isShowNotice = MutableLiveData(false)
    val isShowNotice: LiveData<Boolean>
        get() = _isShowNotice

    private val _isShowMenuPan = MutableLiveData(false)
    val isShowMenuPan: LiveData<Boolean>
        get() = _isShowMenuPan

    private val _totalDonationCan = MutableLiveData(0)
    val totalDonationCan: LiveData<Int>
        get() = _totalDonationCan

    private val _totalHeartCount = MutableLiveData(0)
    val totalHeartCount: LiveData<Int>
        get() = _totalHeartCount

    private val _userProfileLiveData = MutableLiveData<GetLiveRoomUserProfileResponse>()
    val userProfileLiveData: LiveData<GetLiveRoomUserProfileResponse>
        get() = _userProfileLiveData

    private val _coverImageUrlLiveData = MutableLiveData("")
    val coverImageUrlLiveData: LiveData<String>
        get() = _coverImageUrlLiveData

    private val _changeIsAdultLiveData = MutableLiveData(false)
    val changeIsAdultLiveData: LiveData<Boolean>
        get() = _changeIsAdultLiveData

    lateinit var roomInfoResponse: GetRoomInfoResponse

    fun isRoomInfoInitialized() = this::roomInfoResponse.isInitialized

    private var _isLoading = MutableLiveData(false)
    val isLoading: LiveData<Boolean>
        get() = _isLoading

    private var _isBgOn = MutableLiveData(true)
    val isBgOn: LiveData<Boolean>
        get() = _isBgOn

    private var _isSignatureOn = MutableLiveData(true)
    val isSignatureOn: LiveData<Boolean>
        get() = _isSignatureOn

    lateinit var getRealPathFromURI: (Uri) -> String?

    private val blockedMemberIdList: MutableList<Long> = mutableListOf()

    val mutex = Mutex()

    fun getUserNickname(memberId: Int): String {
        for (manager in roomInfoResponse.managerList) {
            if (manager.id.toInt() == memberId) {
                return manager.nickname
            }
        }

        for (speaker in roomInfoResponse.speakerList) {
            if (speaker.id.toInt() == memberId) {
                return speaker.nickname
            }
        }

        for (listener in roomInfoResponse.listenerList) {
            if (listener.id.toInt() == memberId) {
                return listener.nickname
            }
        }

        return ""
    }

    fun getUserProfileUrl(memberId: Int): String {
        for (manager in roomInfoResponse.managerList) {
            if (manager.id.toInt() == memberId) {
                return manager.profileImage
            }
        }

        for (speaker in roomInfoResponse.speakerList) {
            if (speaker.id.toInt() == memberId) {
                return speaker.profileImage
            }
        }

        for (listener in roomInfoResponse.listenerList) {
            if (listener.id.toInt() == memberId) {
                return listener.profileImage
            }
        }

        return ""
    }

    fun getManagerNickname(): String {
        return roomInfoResponse.creatorNickname
    }

    fun setSpeaker(roomId: Long, userId: Long, onSuccess: () -> Unit) {
        compositeDisposable.add(
            repository.setSpeaker(roomId, userId, "Bearer ${SharedPreferenceManager.token}")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.success) {
                            onSuccess()
                        } else {
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
                                )
                            }
                        }
                    },
                    {
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
                    }
                )
        )
    }

    fun setListener(roomId: Long, userId: Long, onSuccess: () -> Unit) {
        compositeDisposable.add(
            repository.setListener(roomId, userId, "Bearer ${SharedPreferenceManager.token}")
                .retry(3)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.success) {
                            onSuccess()
                        } else {
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
                                )
                            }
                        }
                    },
                    {
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
                    }
                )
        )
    }

    fun getRoomInfo(roomId: Long, userId: Int = 0, onSuccess: (String) -> Unit = {}) {
        compositeDisposable.add(
            repository.getRoomInfo(roomId, "Bearer ${SharedPreferenceManager.token}")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.success && it.data != null) {
                            roomInfoResponse = it.data
                            _roomInfoLiveData.postValue(roomInfoResponse)

                            if (_coverImageUrlLiveData.value!! != roomInfoResponse.coverImageUrl) {
                                _coverImageUrlLiveData.value = roomInfoResponse.coverImageUrl
                            }

                            getTotalDonationCan(roomId = roomId)
                            getTotalHeart(roomId = roomId)

                            if (it.data.isAdult && !SharedPreferenceManager.isAuth) {
                                _changeIsAdultLiveData.value = true
                            }

                            if (userId > 0 && it.data.creatorId == SharedPreferenceManager.userId) {
                                val nickname = getUserNickname(userId)
                                onSuccess(nickname)
                            }
                        } else {
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
                                )
                            }
                        }
                    },
                    {
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
                    }
                )
        )
    }

    fun isEqualToHostId(memberId: Int): Boolean {
        return memberId == roomInfoResponse.creatorId.toInt()
    }

    fun isNotBlockedMember(memberId: Long): Boolean {
        return !blockedMemberIdList.contains(memberId)
    }

    fun addBlockedMember(memberId: Long) {
        blockedMemberIdList.add(memberId)
    }

    fun removeBlockedMember(memberId: Long) {
        blockedMemberIdList.remove(memberId)
    }

    fun getBlockedMemberIdList() {
        compositeDisposable.add(
            userRepository.getBlockedMemberIdList(token = "Bearer ${SharedPreferenceManager.token}")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.success && it.data != null) {
                            this.blockedMemberIdList.clear()
                            this.blockedMemberIdList.addAll(it.data)
                        }
                    },
                    {}
                )
        )
    }

    fun getMemberCan() {
        compositeDisposable.add(
            userRepository.getMemberInfo(token = "Bearer ${SharedPreferenceManager.token}")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.success && it.data != null) {
                            SharedPreferenceManager.can = it.data.can
                        }
                    },
                    {}
                )
        )
    }

    fun shareRoomLink(
        roomId: Long,
        isPrivateRoom: Boolean,
        password: String?,
        onSuccess: (String) -> Unit
    ) {
        _isLoading.value = true
        Firebase.dynamicLinks.shortLinkAsync(ShortDynamicLink.Suffix.SHORT) {
            link = Uri.parse("https://sodalive.net/?room_id=$roomId")
            domainUriPrefix = "https://sodalive.page.link"
            androidParameters { }
            iosParameters("kr.co.vividnext.sodalive") {
                appStoreId = "6461721697"
            }
        }.addOnSuccessListener {
            val uri = it.shortLink
            if (uri != null) {
                val message = if (isPrivateRoom) {
                    "${SharedPreferenceManager.nickname}님이 귀하를 " +
                        "보이스온의 비공개라이브에 초대하였습니다.\n" +
                        "※ 라이브 참여: $uri\n" +
                        "(입장 비밀번호 : $password)"
                } else {
                    "${SharedPreferenceManager.nickname}님이 귀하를 " +
                        "보이스온의 공개라이브에 초대하였습니다.\n" +
                        "※ 라이브 참여: $uri"
                }

                onSuccess(message)
            }
        }.addOnFailureListener {
            _toastLiveData.postValue("공유링크를 생성하지 못했습니다.\n다시 시도해 주세요.")
        }.addOnCompleteListener {
            _isLoading.value = false
        }
    }

    fun creatorFollow(creatorId: Long, roomId: Long, isGetUserProfile: Boolean = false) {
        _isLoading.value = true
        compositeDisposable.add(
            repository.creatorFollow(
                creatorId,
                "Bearer ${SharedPreferenceManager.token}"
            )
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.success && it.data != null) {
                            getRoomInfo(roomId)

                            if (isGetUserProfile) {
                                getUserProfile(roomId = roomId, userId = creatorId) {}
                            }
                        } else {
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
                                )
                            }
                        }
                        _isLoading.value = false
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
                    }
                )
        )
    }

    fun creatorUnFollow(creatorId: Long, roomId: Long, isGetUserProfile: Boolean = false) {
        _isLoading.value = true
        compositeDisposable.add(
            repository.creatorUnFollow(
                creatorId,
                "Bearer ${SharedPreferenceManager.token}"
            )
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.success && it.data != null) {
                            getRoomInfo(roomId)

                            if (isGetUserProfile) {
                                getUserProfile(roomId = roomId, userId = creatorId) {}
                            }
                        } else {
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
                                )
                            }
                        }
                        _isLoading.value = false
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
                    }
                )
        )
    }

    fun toggleShowNotice() {
        _isShowMenuPan.value = false
        _isShowNotice.value = !isShowNotice.value!!
    }

    fun toggleShowMenuPan(isShowMenuPan: Boolean? = null) {
        _isShowNotice.value = false

        if (isShowMenuPan != null) {
            _isShowMenuPan.value = isShowMenuPan
        } else {
            _isShowMenuPan.value = !this.isShowMenuPan.value!!
        }
    }

    fun toggleBackgroundImage() {
        _isBgOn.value = !isBgOn.value!!
    }

    fun toggleSignatureImage() {
        _isSignatureOn.value = !isSignatureOn.value!!
    }

    fun editLiveRoomInfo(
        roomId: Long,
        newTitle: String,
        newContent: String,
        newCoverImageUri: Uri? = null,
        isActivateMenu: Boolean?,
        menuId: Long,
        menu: String,
        isAdult: Boolean?,
        onSuccess: () -> Unit
    ) {
        val request = EditLiveRoomInfoRequest(
            title = if (newTitle != roomInfoResponse.title) {
                newTitle
            } else {
                null
            },
            notice = if (newContent != roomInfoResponse.notice) {
                newContent
            } else {
                null
            },
            numberOfPeople = null,
            beginDateTimeString = null,
            timezone = null,
            menuPanId = if (isActivateMenu == true) menuId else 0,
            menuPan = if (isActivateMenu == true) menu else "",
            isActiveMenuPan = isActivateMenu,
            isAdult = isAdult
        )

        val requestJson = if (
            request.title != null ||
            request.notice != null ||
            request.isActiveMenuPan != null ||
            request.isAdult != null ||
            request.menuPan.isNotBlank()
        ) {
            Gson().toJson(request)
        } else {
            null
        }

        val coverImage = if (newCoverImageUri != null) {
            val file = File(getRealPathFromURI(newCoverImageUri))
            MultipartBody.Part.createFormData(
                "coverImage",
                file.name,
                file.asRequestBody("image/*".toMediaType())
            )
        } else {
            null
        }

        if (coverImage == null && requestJson == null) {
            _toastLiveData.value = "변경사항이 없습니다."
            return
        }

        compositeDisposable.add(
            repository.editLiveRoomInfo(
                roomId = roomId,
                coverImage = coverImage,
                request = requestJson?.toRequestBody("text/plain".toMediaType()),
                token = "Bearer ${SharedPreferenceManager.token}"
            )
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.success) {
                            getRoomInfo(roomId = roomId)
                            onSuccess()
                        } else {
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "라이브 정보를 수정하지 못했습니다.\n다시 시도해 주세요."
                                )
                            }
                        }
                    },
                    {
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue(
                            "라이브 정보를 수정하지 못했습니다.\n다시 시도해 주세요."
                        )
                    }
                )
        )
    }

    fun quitRoom(roomId: Long, onSuccess: () -> Unit) {
        _isLoading.value = true
        compositeDisposable.add(
            repository.quitRoom(roomId = roomId, token = "Bearer ${SharedPreferenceManager.token}")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.success) {
                            onSuccess()
                            _isLoading.value = false
                        } else {
                            _isLoading.value = false
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
                                )
                            }
                        }
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
                    }
                )
        )
    }

    fun kickOut(roomId: Long, userId: Long) {
        compositeDisposable.add(
            repository.kickOut(roomId, userId, token = "Bearer ${SharedPreferenceManager.token}")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({}, {})
        )
    }

    fun likeHeart(roomId: Long, onSuccess: () -> Unit, onFailure: () -> Unit) {
        compositeDisposable.add(
            repository.likeHeart(roomId, token = "Bearer ${SharedPreferenceManager.token}")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.success) {
                            SharedPreferenceManager.can -= 1
                            onSuccess()
                        } else {
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
                                )
                            }
                            onFailure()
                        }
                    },
                    {
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
                        onFailure()
                    }
                )
        )
    }

    fun donation(
        roomId: Long,
        can: Int,
        message: String,
        isSecret: Boolean,
        onSuccess: (LiveRoomDonationResponse?) -> Unit
    ) {
        _isLoading.postValue(true)
        compositeDisposable.add(
            repository.donation(
                roomId,
                can,
                message,
                isSecret,
                "Bearer ${SharedPreferenceManager.token}"
            )
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        _isLoading.value = false
                        if (it.success) {
                            SharedPreferenceManager.can -= can
                            onSuccess(it.data)
                        } else {
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
                                )
                            }
                        }
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
                    }
                )
        )
    }

    fun refundDonation(roomId: Long) {
        _isLoading.postValue(true)

        compositeDisposable.add(
            repository.refundDonation(roomId, "Bearer ${SharedPreferenceManager.token}")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        _isLoading.value = false
                        if (it.success) {
                            _toastLiveData.postValue(
                                "후원에 실패했습니다.\n다시 후원해주세요.\n" +
                                    "계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
                            )
                        } else {
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "후원에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요."
                                )
                            }
                        }
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue(
                            "후원에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요."
                        )
                    }
                )
        )
    }

    suspend fun addDonationCan(can: Int) {
        mutex.withLock {
            _totalDonationCan.postValue(totalDonationCan.value!! + can)
        }
    }

    suspend fun addHeartDonation() {
        mutex.withLock {
            _totalHeartCount.postValue(totalHeartCount.value!! + 1)
        }
    }

    fun donationStatus(roomId: Long, onSuccess: (GetLiveRoomDonationStatusResponse) -> Unit) {
        _isLoading.value = true
        compositeDisposable.add(
            repository.donationStatus(roomId, token = "Bearer ${SharedPreferenceManager.token}")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        _isLoading.value = false
                        if (it.success && it.data != null) {
                            onSuccess(it.data)
                        } else {
                            _toastLiveData.postValue(
                                "후원현황을 가져오지 못했습니다\n다시 시도해 주세요."
                            )
                        }
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue(
                            "후원현황을 가져오지 못했습니다\n다시 시도해 주세요."
                        )
                    }
                )
        )
    }

    fun heartStatus(roomId: Long, onSuccess: (GetLiveRoomHeartListResponse) -> Unit) {
        _isLoading.value = true
        compositeDisposable.add(
            repository.heartStatus(roomId, token = "Bearer ${SharedPreferenceManager.token}")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        _isLoading.value = false
                        if (it.success && it.data != null) {
                            onSuccess(it.data)
                        } else {
                            _toastLiveData.postValue(
                                "하트 현황을 가져오지 못했습니다\n다시 시도해 주세요."
                            )
                        }
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue(
                            "하트 현황을 가져오지 못했습니다\n다시 시도해 주세요."
                        )
                    }
                )
        )
    }

    fun getUserRank(userId: Long): Int {
        // 방장 -> -2
        // 스탭 -> -3
        // 나머지 -> 체크
        return if (isEqualToHostId(userId.toInt())) {
            -2
        } else if (isEqualToManagerId(userId.toInt())) {
            -3
        } else {
            roomInfoResponse.donationRankingTop3UserIds.indexOf(userId)
        }
    }

    private fun getTotalHeart(roomId: Long) {
        compositeDisposable.add(
            repository.getTotalHeartCount(
                roomId = roomId,
                token = "Bearer ${SharedPreferenceManager.token}"
            )
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.success && it.data != null) {
                            _totalHeartCount.postValue(it.data.totalHeartCount)
                        }
                    },
                    {
                        _totalHeartCount.postValue(0)
                    }
                )
        )
    }

    private fun getTotalDonationCan(roomId: Long) {
        compositeDisposable.add(
            repository.getTotalDonationCan(
                roomId = roomId,
                token = "Bearer ${SharedPreferenceManager.token}"
            )
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.success && it.data != null) {
                            _totalDonationCan.postValue(it.data.totalDonationCan)
                        }
                    },
                    {
                    }
                )
        )
    }

    fun getUserProfile(roomId: Long, userId: Long, onSuccess: () -> Unit) {
        _isLoading.value = true
        compositeDisposable.add(
            repository.getUserProfile(
                roomId = roomId,
                userId = userId,
                token = "Bearer ${SharedPreferenceManager.token}"
            )
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        _isLoading.value = false
                        if (it.success && it.data != null) {
                            _userProfileLiveData.value = it.data!!
                            onSuccess()
                        } else {
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
                                )
                            }
                        }
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
                    }
                )
        )
    }

    fun setManager(roomId: Long, userId: Long, onSuccess: () -> Unit) {
        _isLoading.value = true
        compositeDisposable.add(
            repository.setManager(roomId, userId, "Bearer ${SharedPreferenceManager.token}")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        _isLoading.value = false
                        if (it.success) {
                            getRoomInfo(roomId)
                            onSuccess()
                        } else {
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
                                )
                            }
                        }
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
                    }
                )
        )
    }

    fun isEqualToManagerId(memberId: Int): Boolean {
        for (manager in roomInfoResponse.managerList) {
            if (manager.id == memberId.toLong()) {
                return true
            }
        }

        return false
    }

    fun memberBlock(userId: Long, onSuccess: () -> Unit) {
        _isLoading.value = true
        compositeDisposable.add(
            userRepository.memberBlock(
                userId = userId,
                token = "Bearer ${SharedPreferenceManager.token}"
            )
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        _isLoading.value = false

                        if (it.success) {
                            _toastLiveData.postValue("차단하였습니다.")
                            onSuccess()
                        } else {
                            val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
                            _toastLiveData.postValue(message)
                        }
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
                    }
                )
        )
    }

    fun memberUnBlock(userId: Long) {
        _isLoading.value = true
        compositeDisposable.add(
            userRepository.memberUnBlock(
                userId = userId,
                token = "Bearer ${SharedPreferenceManager.token}"
            )
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        _isLoading.value = false

                        if (it.success) {
                            getUserProfile(roomId = roomInfoResponse.roomId, userId = userId) {}
                            _toastLiveData.postValue("차단이 해제 되었습니다.")
                        } else {
                            val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
                            _toastLiveData.postValue(message)
                        }
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
                    }
                )
        )
    }

    fun report(type: ReportType, userId: Long, reason: String = "프로필 신고") {
        _isLoading.value = true

        val request = ReportRequest(type, reason, reportedMemberId = userId)
        compositeDisposable.add(
            reportRepository.report(
                request = request,
                token = "Bearer ${SharedPreferenceManager.token}"
            )
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.message != null) {
                            _toastLiveData.postValue(it.message)
                        } else {
                            _toastLiveData.postValue(
                                "신고가 접수되었습니다."
                            )
                        }

                        _isLoading.value = false
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("신고가 접수되었습니다.")
                    }
                )
        )
    }

    fun showRoulette(complete: (List<RoulettePreview>) -> Unit) {
        if (!_isLoading.value!!) {
            _isLoading.value = true
            compositeDisposable.add(
                rouletteRepository.getRoulette(
                    creatorId = roomInfoResponse.creatorId,
                    token = "Bearer ${SharedPreferenceManager.token}"
                )
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(
                        {
                            _isLoading.value = false

                            val data = it.data
                            if (
                                it.success &&
                                !data.isNullOrEmpty()
                            ) {
                                complete(
                                    data
                                        .filter { roulette -> roulette.isActive }
                                        .filter { roulette -> roulette.items.isNotEmpty() }
                                        .map { roulette ->
                                            RoulettePreview(
                                                id = roulette.id,
                                                can = roulette.can,
                                                items = calculatePercentages(roulette.items)
                                            )
                                        }
                                )
                            } else {
                                val message = it.message ?: "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
                                _toastLiveData.postValue(message)
                            }
                        },
                        {
                            _isLoading.value = false
                            it.message?.let { message -> Logger.e(message) }
                            _toastLiveData.postValue("룰렛을 사용할 수 없습니다. 다시 시도해 주세요.")
                        }
                    )
            )
        }
    }

    fun spinRoulette(
        roomId: Long,
        rouletteId: Long,
        complete: (Int, List<RouletteItem>, String) -> Unit
    ) {
        if (!_isLoading.value!!) {
            _isLoading.value = true
            compositeDisposable.add(
                rouletteRepository.spinRoulette(
                    request = SpinRouletteRequest(roomId = roomId, rouletteId = rouletteId),
                    token = "Bearer ${SharedPreferenceManager.token}"
                )
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(
                        {
                            _isLoading.value = false

                            val data = it.data
                            if (
                                it.success &&
                                data != null &&
                                data.items.isNotEmpty()
                            ) {
                                SharedPreferenceManager.can -= data.can
                                complete(data.can, data.items, data.result)
                            } else {
                                val message = it.message ?: "룰렛을 사용할 수 없습니다. 다시 시도해 주세요."
                                _toastLiveData.postValue(message)
                            }
                        },
                        {
                            _isLoading.value = false
                            it.message?.let { message -> Logger.e(message) }
                            _toastLiveData.postValue("룰렛을 사용할 수 없습니다. 다시 시도해 주세요.")
                        }
                    )
            )
        }
    }

    fun refundRouletteDonation(roomId: Long) {
        _isLoading.postValue(true)

        compositeDisposable.add(
            rouletteRepository.refundRouletteDonation(
                roomId,
                "Bearer ${SharedPreferenceManager.token}"
            )
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        _isLoading.value = false
                        if (it.success) {
                            _toastLiveData.postValue(
                                "후원에 실패했습니다.\n다시 후원해주세요.\n" +
                                    "계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
                            )
                        } else {
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "후원에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요."
                                )
                            }
                        }
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue(
                            "후원에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요."
                        )
                    }
                )
        )
    }

    fun getAllMenuPreset(onSuccess: (List<GetMenuPresetResponse>) -> Unit) {
        _isLoading.value = true

        compositeDisposable.add(
            repository.getAllMenu(
                creatorId = SharedPreferenceManager.userId,
                token = "Bearer ${SharedPreferenceManager.token}"
            )
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    {
                        if (it.success) {
                            onSuccess(it.data ?: listOf())
                        } else {
                            if (it.message != null) {
                                _toastLiveData.postValue(it.message)
                            } else {
                                _toastLiveData.postValue(
                                    "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
                                )
                            }
                        }
                        _isLoading.value = false
                    },
                    {
                        _isLoading.value = false
                        it.message?.let { message -> Logger.e(message) }
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
                    }
                )
        )
    }

    private fun calculatePercentages(options: List<RouletteItem>): List<RoulettePreviewItem> {
        val updatedOptions = options.map { option ->
            RoulettePreviewItem(
                title = option.title,
                percent = "${String.format(Locale.KOREAN, "%.2f", option.percentage)}%"
            )
        }.toList()

        return updatedOptions
    }
}
