//
//  LNRoomSession.swift
//  Gami
//
//  Created by OneeChan on 2026/3/9.
//

import Foundation
import AtomicXCore
import Combine


protocol LNRoomViewModelNotify {
    // 公屏事件
    func onRoomMessageChanged(session: LNRoomSession, messages: [LNRoomMessageItem])
    
    // 麦位事件
    func onRoomSeatsChanged(session: LNRoomSession, changed: [LNRoomSeatItem])
    func onRoomSpeakingUsersChanged(session: LNRoomSession, speakings: [String])
    func onRoomSeatApplyChanged(session: LNRoomSession, applyCount: Int)
    
    // 用户自身事件
    func onRoomUserDidJoinOnSeat(session: LNRoomSession, newSeat: LNRoomSeatItem)
    func onRoomUserDidLeaveSeat(session: LNRoomSession)
    func onRoomUserDidApplySeat(session: LNRoomSession, index: Int)
    func onRoomUserDidCancelApplySeat(session: LNRoomSession)
    
    // 房间配置事件
    func onRoomInfoChanged(session: LNRoomSession, roomInfo: LNRoomInfo)
    
    // 房间关闭
    func onRoomClosed(session: LNRoomSession)
}
extension LNRoomViewModelNotify {
    func onRoomMessageChanged(session: LNRoomSession, messages: [LNRoomMessageItem]) { }
    
    func onRoomSeatsChanged(session: LNRoomSession, changed: [LNRoomSeatItem]) { }
    func onRoomSpeakingUsersChanged(session: LNRoomSession, speakings: [String]) { }
    func onRoomSeatApplyChanged(session: LNRoomSession, applyCount: Int) { }
    
    func onRoomUserDidJoinOnSeat(session: LNRoomSession, newSeat: LNRoomSeatItem) { }
    func onRoomUserDidLeaveSeat(session: LNRoomSession) { }
    func onRoomUserDidApplySeat(session: LNRoomSession, index: Int) { }
    func onRoomUserDidCancelApplySeat(session: LNRoomSession) { }
    
    func onRoomInfoChanged(session: LNRoomSession, roomInfo: LNRoomInfo) { }
    
    func onRoomClosed(session: LNRoomSession) { }
}


enum LNApplySeatErrCode: Int {
    case noPermission = 50001
}

class LNRoomSession: NSObject {
    let roomId: String
    private let seatStore: LiveSeatStore
    private let guestStore: CoGuestStore
    private let messageStore: BarrageStore
    private let audienceStore: LiveAudienceStore
    private let systemHandlerQueue = DispatchQueue(label: "com.gami.room.system.message", attributes: .concurrent)
    
    private(set) var seatsInfo: [LNRoomSeatItem] = []
    private(set) var roomInfo = LNRoomInfo()
    private(set) var speakingUser: [String] = []
    private(set) var seatApplyCount: Int = 0
    
    private var lastmessage: Barrage? = nil
    private var isLoadingGiftList = false
    private(set) var giftList: [LNGiftItemVO] = []
    
    init(roomId: String) {
        self.roomId = roomId
        
        seatStore = LiveSeatStore.create(liveID: roomId)
        guestStore = CoGuestStore.create(liveID: roomId)
        messageStore = BarrageStore.create(liveID: roomId)
        audienceStore = LiveAudienceStore.create(liveID: roomId)
        
        super.init()
        LNEventDeliver.addObserver(self)
        
        setupSeatObservers()
        setupMessageObservers()
        setupRoomInfoObserver()
        setupAudienceEventObservers()
        reloadGiftList()
        
        LNRoomManager.shared.updateCurRoom(self)
    }
    
    func closeRoom() {
        LNRoomManager.shared.closeRoom { success in
            LNEventDeliver.notifyEvent { ($0 as? LNRoomViewModelNotify)?.onRoomClosed(session: self) }
        }
    }
    
    func leaveRoom() {
        LNRoomManager.shared.leaveRoom { success in
            LNEventDeliver.notifyEvent { ($0 as? LNRoomViewModelNotify)?.onRoomClosed(session: self) }
        }
    }
    
    func handleSystemMessage(message: LNRoomPushMessage) { }
}

// MARK: 麦位管理 - 普通用户
extension LNRoomSession {
    var mySeatInfo: LNRoomSeatItem? {
        seatsInfo.first { $0.uid.isMyUid }
    }
    
    private func setupSeatObservers() {
        seatStore.state.subscribe().receive(on: DispatchQueue.main).sink { [weak self] state in
            guard let self else { return }
            let wasOnMic = mySeatInfo != nil
            
            var hasChanged = state.seatList.count != seatsInfo.count
            var newSeats: [LNRoomSeatItem] = []
            var changedSeats: [LNRoomSeatItem] = []
            for seat in state.seatList {
                let item = seatsInfo.first(where: { $0.index == seat.index })
                ?? LNRoomSeatItem(index: seat.index)
                if item.update(seat) {
                    changedSeats.append(item)
                    hasChanged = true
                }
                newSeats.append(item)
            }
            if hasChanged {
                seatsInfo = newSeats
                if !wasOnMic, let mySeatInfo {
                    // 自己上麦
                    if roomInfo.forbidAudio {
                        DeviceStore.shared.closeLocalMicrophone()
                    } else {
                        DeviceStore.shared.openLocalMicrophone(completion: nil)
                    }
                    
                    LNEventDeliver.notifyEvent { ($0 as? LNRoomViewModelNotify)?.onRoomUserDidJoinOnSeat(session: self, newSeat: mySeatInfo) }
                }
                LNEventDeliver.notifyEvent { ($0 as? LNRoomViewModelNotify)?.onRoomSeatsChanged(session: self, changed: changedSeats) }
            }
            
            let speakings = state.speakingUsers.filter { $0.value > 0 }.map { $0.key }.sorted { $1 > $0 }
            if speakings != speakingUser {
                speakingUser = speakings
                LNEventDeliver.notifyEvent { ($0 as? LNRoomViewModelNotify)?.onRoomSpeakingUsersChanged(session: self, speakings: speakings) }
            }
        }.store(in: &cancellables)
    }
    
    // 申请上麦
    func applySeat(index: Int, handler: @escaping (Bool) -> Void) {
        LNPermissionHelper.requestMicrophoneAccess { [weak self] success in
            guard let self else { return }
            guard success else {
                showToast(.init(key: "A00362"))
                return
            }
            LNHttpManager.shared.applySeat(roomId: roomId, index: index) { [weak self] err in
                runOnMain {
                    handler(err == nil)
                }
                if let err {
                    if case .serverError(let code, _) = err,
                       code == LNApplySeatErrCode.noPermission.rawValue {
                        showToast(err.errorDesc, icon: .icAbout.withTintColor(.fill))
                    } else {
                        showToast(err.errorDesc)
                    }
                } else if let self {
                    LNEventDeliver.notifyEvent {
                        ($0 as? LNRoomViewModelNotify)?.onRoomUserDidApplySeat(session: self, index: index)
                    }
                }
            }
        }
    }
    
    // 取消上麦申请
    func cancelSeatApply(handler: @escaping (Bool) -> Void) {
        LNHttpManager.shared.cancelApplySeat(roomId: roomId) { [weak self] err in
            runOnMain {
                handler(err == nil)
            }
            if let err {
                showToast(err.errorDesc)
            } else if let self {
                LNEventDeliver.notifyEvent {
                    ($0 as? LNRoomViewModelNotify)?.onRoomUserDidCancelApplySeat(session: self)
                }
            }
        }
    }
    
    // 主动下麦
    func leaveSeat(handler: @escaping (Bool) -> Void) {
        seatStore.leaveSeat { [weak self] result in
            guard let self else { return }
            switch result {
            case .success:
                handler(true)
                LNEventDeliver.notifyEvent {
                    ($0 as? LNRoomViewModelNotify)?.onRoomUserDidLeaveSeat(session: self)
                }
            case .failure(let err):
                showTencentError(err)
                handler(false)
            }
        }
    }
}


// MARK: 麦位管理 - 管理员
extension LNRoomSession {
    // 踢人下麦
    func kickUserOffSeat(uid: String, handler: @escaping (Bool) -> Void) {
        seatStore.kickUserOutOfSeat(userID: uid) { result in
            switch result {
            case .success:
                handler(true)
            case .failure(let err):
                showTencentError(err)
                handler(false)
            }
        }
    }
    
    func clearApplyList(type: LNRoomApplySeatType, handler: @escaping (Bool) -> Void) {
        LNHttpManager.shared.clearApplySeatList(roomId: roomId, searchType: type) { err in
            runOnMain {
                handler(err == nil)
            }
            if let err {
                showToast(err.errorDesc)
            }
        }
    }
    
    // 接受上麦申请
    func acceptSeatApply(applyId: String, handler: @escaping (Bool) -> Void) {
        LNHttpManager.shared.handleApplySeat(roomId: roomId, applyId: applyId, accept: true) { err in
            runOnMain {
                handler(err == nil)
            }
            if let err {
                showToast(err.errorDesc)
            }
        }
    }
    
    // 拒绝上麦申请
    func rejectSeatApply(applyId: String, handler: @escaping (Bool) -> Void) {
        LNHttpManager.shared.handleApplySeat(roomId: roomId, applyId: applyId, accept: false) { err in
            runOnMain {
                handler(err == nil)
            }
            if let err {
                showToast(err.errorDesc)
            }
        }
    }
    
    // 邀请上麦
    func inviteUserToSeat(uid: String, index: Int, handler: @escaping (Bool) -> Void) {
        LNHttpManager.shared.inviteUserToSeat(roomId: roomId, uid: uid, index: index) { err in
            runOnMain {
                handler(err == nil)
            }
            if let err {
                showToast(err.errorDesc)
            }
        }
    }
    
    // 关闭麦位
    func lockSeat(num: Int, handler: @escaping (Bool) -> Void) {
        seatStore.lockSeat(seatIndex: num) { result in
            switch result {
            case .success:
                handler(true)
            case .failure(let err):
                showTencentError(err)
                handler(false)
            }
        }
    }
    
    // 解锁麦位
    func unlockSeat(num: Int, handler: @escaping (Bool) -> Void) {
        seatStore.unlockSeat(seatIndex: num) { result in
            switch result {
            case .success:
                handler(true)
            case .failure(let err):
                showTencentError(err)
                handler(false)
            }
        }
    }
}

// MARK: 麦克风管理 - 普通用户
extension LNRoomSession {
    // 关闭自己麦克风
    func muteMySeat() {
        seatStore.muteMicrophone()
    }
    
    // 打开自己麦克风
    func unmuteMySeat(handler: @escaping (Bool) -> Void) {
        let unmute = { [weak self] in
            guard let self else { return }
            seatStore.unmuteMicrophone { result in
                switch result {
                case .success:
                    handler(true)
                case .failure(let err):
                    showTencentError(err)
                    handler(false)
                }
            }
        }
        if DeviceStore.shared.state.value.microphoneStatus == .off {
            DeviceStore.shared.openLocalMicrophone { result in
                switch result {
                case .success:
                    unmute()
                case .failure(let err):
                    showTencentError(err)
                    handler(false)
                }
            }
        } else {
            unmute()
        }
    }
}

// MARK: 麦克风管理 - 管理员
extension LNRoomSession {
    // 禁止某人的麦克风
    func muteSeat(uid: String, handler: @escaping (Bool) -> Void) {
        seatStore.closeRemoteMicrophone(userID: uid) { result in
            switch result {
            case .success:
                handler(true)
            case .failure(let err):
                showTencentError(err)
                handler(false)
            }
        }
    }
    
    // 解锁某人麦克风
    func unmuteSeat(uid: String, handler: @escaping (Bool) -> Void) {
        seatStore.openRemoteMicrophone(userID: uid, policy: .unlockOnly) { result in
            switch result {
            case .success:
                handler(true)
            case .failure(let err):
                showTencentError(err)
                handler(false)
            }
        }
    }
}

// MARK: 公屏
extension LNRoomSession {
    private func setupMessageObservers() {
        messageStore.state.subscribe().receive(on: DispatchQueue.main).sink { [weak self] state in
            guard let self else { return }
            var newMessage: [Barrage] = []
            if let lastId = lastmessage?.sequence,
               let index = state.messageList.lastIndex(where: { $0.sequence == lastId }) {
                newMessage.append(contentsOf: state.messageList[(index + 1)...])
            } else {
                newMessage.append(contentsOf: state.messageList)
            }
            lastmessage = newMessage.last
            
            systemHandlerQueue.async { [weak self] in
                guard let self else { return }
                var chatMessages: [LNRoomMessageItem] = []
                var systemMessages: [LNRoomPushMessage] = []
                newMessage.forEach {
                    switch $0.messageType {
                    case .text:
                        if let messageItems = LNRoomUserMessage(info: $0)?.messageItems,
                           !messageItems.isEmpty {
                            chatMessages.append(contentsOf: messageItems)
                        }
                    case .custom:
                        if let item = LNRoomPushMessage(info: $0) {
                            systemMessages.append(item)
                            
                            if let messageItems = item.messageItems,
                                !messageItems.isEmpty {
                                chatMessages.append(contentsOf: messageItems)
                            }
                        }
                    default:
                        break
                    }
                }
                if !chatMessages.isEmpty {
                    LNEventDeliver.notifyEvent { ($0 as? LNRoomViewModelNotify)?.onRoomMessageChanged(session: self, messages: chatMessages) }
                }
                systemMessages.forEach {
                    self.handleSystemMessage(message: $0)
                }
            }
        }.store(in: &cancellables)
    }
    
    func sendChatMessage(text: String, handler: @escaping (Bool) -> Void) {
        messageStore.sendTextMessage(text: text, extensionInfo: [
            LNRoomChatMessageTypeKey: LNRoomChatMessageType.chat.rawValue
        ]) { result in
            switch result {
            case .success:
                handler(true)
            case .failure(let err):
                showTencentError(err)
                handler(false)
            }
        }
    }
}

// MARK: 房间信息
extension LNRoomSession {
    private func setupRoomInfoObserver() {
        LNRoomManager.shared.liveListStore.state.subscribe().receive(on: DispatchQueue.main).sink { [weak self] state in
            guard let self else { return }
            if state.currentLive.liveID.isEmpty { return }
            
            if !state.currentLive.roomType.contains(.playmate) {
                showToast(.init(key: "A00389"))
                leaveRoom()
                return
            }
            if roomInfo.update(state.currentLive) {
                LNEventDeliver.notifyEvent { ($0 as? LNRoomViewModelNotify)?.onRoomInfoChanged(session: self, roomInfo: self.roomInfo) }
            }
            
            let oldCount = seatApplyCount
            if state.currentLive.applySeatCount != seatApplyCount {
                seatApplyCount = state.currentLive.applySeatCount
            }
            if oldCount != seatApplyCount {
                LNEventDeliver.notifyEvent { ($0 as? LNRoomViewModelNotify)?.onRoomSeatApplyChanged(session: self, applyCount: self.seatApplyCount) }
            }
        }.store(in: &cancellables)
        LNRoomManager.shared.liveListStore.liveListEventPublisher.receive(on: DispatchQueue.main).sink { [weak self] event in
            guard let self else { return }
            switch event {
            case .onLiveEnded(let roomId, _, _):
                if self.roomId == roomId {
                    LNEventDeliver.notifyEvent { ($0 as? LNRoomViewModelNotify)?.onRoomClosed(session: self) }
                }
            case .onKickedOutOfLive(let roomId, _, _):
                if self.roomId == roomId {
                    LNEventDeliver.notifyEvent { ($0 as? LNRoomViewModelNotify)?.onRoomClosed(session: self) }
                }
            default: break
            }
        }.store(in: &cancellables)
    }
    
    func updateRoomInfo(name: String, cover: String, forbidAudio: Bool, handler: @escaping (Bool) -> Void) {
        LNHttpManager.shared.updateRoom(roomId: roomId, roomTitle: name,
                                        roomCover: cover, forbidAudio: forbidAudio) { err in
            runOnMain {
                handler(err == nil)
            }
            if let err {
                showToast(err.errorDesc)
            }
        }
    }
}

// MARK: 观众事件
extension LNRoomSession {
    private func setupAudienceEventObservers() {
        audienceStore.liveAudienceEventPublisher.receive(on: DispatchQueue.main).sink { [weak self] event in
            guard let self else { return }
            switch event {
            case .onAudienceJoined(let info):
                messageStore.appendLocalTip(message: Barrage.welcomeMessage(userInfo: info))
            default:
                break
            }
        }.store(in: &cancellables)
    }
}

// MARK: 礼物
extension LNRoomSession {
    func sendGift(gift: LNGiftItemVO, to: [String], count: Int, handler: @escaping (Bool) -> Void) {
        let param = LNSendGiftParams()
        param.roomId = roomId
        param.giftId = gift.id
        param.userIds = to
        param.quantity = count
        LNGiftManager.shared.sendGift(params: param) { success in
            handler(success)
        }
    }
    
    func reloadGiftList() {
        guard !isLoadingGiftList else { return }
        isLoadingGiftList = true
        LNGiftManager.shared.fetchGiftList(roomId: roomId) { [weak self] list in
            guard let self else { return }
            if let list, !list.isEmpty  {
                giftList = list
            }
            isLoadingGiftList = false
        }
    }
}
