//
//  LNProfileManager.swift
//  Lanu
//
//  Created by OneeChan on 2025/11/6.
//

import Foundation


protocol LNProfileManagerNotify {
    func onUserInfoChanged(userInfo: LNUserProfileVO)
    func onBindPhoneCaptchaCoolDownChanged(time: Int)
    func onUserVoiceBarInfoChanged()
}
extension LNProfileManagerNotify {
    func onUserInfoChanged(userInfo: LNUserProfileVO) { }
    func onBindPhoneCaptchaCoolDownChanged(time: Int) { }
    func onUserVoiceBarInfoChanged() { }
}

var myUserInfo: LNUserProfileVO {
    LNProfileManager.shared.myUserInfo
}

var myVoiceBarInfo: LNUserVoiceStateVO {
    LNProfileManager.shared.myVoiceState
}

class LNProfileUserInfo {
    var uid: String = ""
    var name: String = ""
    var avatar: String = ""
}

class LNProfileManager {
    static let shared = LNProfileManager()
    static let nameMaxInput = 25
    static let bioMaxInput = 100
    
    fileprivate var myUserInfo: LNUserProfileVO = LNUserProfileVO()
    fileprivate var myVoiceState: LNUserVoiceStateVO = LNUserVoiceStateVO()
    
    private var randomProfile: LNRandomProfileResponse?
    
    private static let profileCacheLimit = 300
    private var profileCache: [String: LNProfileUserInfo] = [:]
    private var profileCacheOrder: [String] = []
    private let profileCacheLock = NSLock()
    
    private let captchaCoolDown = 60
    private var captchaRemain = 0
    private var captchaTimer: Timer?
    var canSendCaptcha: Bool {
        captchaRemain == 0
    }
    
    private init() {
        LNEventDeliver.addObserver(self)
    }
}

extension LNProfileManager {
    func reloadMyProfileDetail() {
        LNHttpManager.shared.getMyProfileDetail { [weak self] res, err in
            guard let self else { return }
            guard err == nil, let res else { return }
            if let relation = res.userFollowStat {
                LNRelationManager.shared.updateMyRelationInfo(relation)
            }
            if let voice = res.userVoice {
                myVoiceState = voice
                notifyUserVoiceBarInfoChanged()
            }
        }
        getUserProfileDetail(uid: myUid) { _ in }
    }
    
    func modifyMyProfile(config: LNProfileUpdateConfig, queue: DispatchQueue = .main,
                         handler: @escaping (Bool) -> Void) {
        LNHttpManager.shared.modifyMyProfile(
            config: config) { [weak self] err in
            guard let self else { return }
            if let err {
                showToast(err.errorDesc)
            } else {
                reloadMyProfileDetail()
                if config.interest != nil {
                    LNGameMateManager.shared.getGameTypeList { _ in }
                }
            }
            queue.asyncIfNotGlobal {
                handler(err == nil)
            }
        }
    }
    
    func setMyVoiceBar(url: String, duration: Int, queue: DispatchQueue = .main,
                       handler: @escaping (Bool) -> Void) {
        LNHttpManager.shared.setMyVoiceBar(url: url, duration: duration) { [weak self] err in
            queue.asyncIfNotGlobal {
                handler(err == nil)
            }
            if let err {
                showToast(err.errorDesc)
            } else {
                guard let self else { return }
                reloadMyProfileDetail()
            }
        }
    }
    
    func cleanVoiceBar(queue: DispatchQueue = .main, handler: @escaping (Bool) -> Void) {
        LNHttpManager.shared.cleanVoiceBar { [weak self] err in
            queue.asyncIfNotGlobal {
                handler(err == nil)
            }
            if let err {
                showToast(err.errorDesc)
            } else {
                guard let self else { return }
                reloadMyProfileDetail()
            }
        }
    }
}

extension LNProfileManager {
    func getUserProfileDetail(uid: String, queue: DispatchQueue = .main, handler: @escaping (LNUserProfileVO?) -> Void) {
        LNHttpManager.shared.getUsersInfo(uids: [uid]) { [weak self] res, err in
            guard let self else { return }
            if let res, err == nil {
                res.list.forEach {
                    if $0.userNo.isMyUid {
                        if self.myUserInfo.update($0) {
                            self.notifyUserInfoChanged(newInfo: $0)
                        }
                    } else {
                        self.updateProfileCache(
                            uid: $0.userNo,
                            name: $0.nickname,
                            avatar: $0.avatar
                        )
                        self.notifyUserInfoChanged(newInfo: $0)
                    }
                }
            } else {
                showToast(err?.errorDesc)
            }
            queue.asyncIfNotGlobal {
                handler(res?.list.first)
            }
        }
    }
    
    func getRandomProfile(queue: DispatchQueue = .main,
                          handler: @escaping (LNProfileRandomInfoVO?, LNProfileRandomInfoVO?) -> Void) {
        if let randomProfile {
            handler(randomProfile.male, randomProfile.female)
            return
        }
        LNHttpManager.shared.getRandomProfile { [weak self] res, err in
            if let self, let res {
                randomProfile = res
            }
            queue.asyncIfNotGlobal {
                handler(res?.male, res?.female)
            }
        }
    }
    
//    func getUserOnlineState(uids: [String], queue: DispatchQueue = .main, handler: @escaping ([String: Bool]) -> Void) {
//        LNHttpManager.shared.getUserOnlineState(uids: uids) { res, err in
//            queue.asyncIfNotGlobal {
//                handler(res?.list.reduce(into: [String: Bool](), { partialResult, state in
//                    partialResult[state.userNo] = state.online
//                }) ?? [:])
//            }
//        }
//    }
}

extension LNProfileManager {
    func getCachedProfileUserInfo(uid: String) -> LNProfileUserInfo? {
        profileCacheLock.lock()
        defer { profileCacheLock.unlock() }
        guard let info = profileCache[uid] else {
            return nil
        }
        touchProfileCacheKey(uid)
        return info
    }
    
    func getCachedProfileUserInfo(uid: String,
                                  fetchIfNeeded: Bool = true,
                                  queue: DispatchQueue = .main,
                                  handler: @escaping (LNProfileUserInfo?) -> Void) {
        if let info = getCachedProfileUserInfo(uid: uid) {
            queue.asyncIfNotGlobal {
                handler(info)
            }
            return
        }
        
        guard fetchIfNeeded else {
            queue.asyncIfNotGlobal {
                handler(nil)
            }
            return
        }
        
        getUserProfileDetail(uid: uid, queue: queue) { [weak self] profile in
            guard let self, let profile else {
                handler(nil)
                return
            }
            
            self.updateProfileCache(
                uid: profile.userNo,
                name: profile.nickname,
                avatar: profile.avatar
            )
            handler(self.getCachedProfileUserInfo(uid: uid))
        }
    }
    
    func updateProfileCache(uid: String,
                            name: String,
                            avatar: String) {
        guard !uid.isEmpty else {
            return
        }
        
        profileCacheLock.lock()
        defer { profileCacheLock.unlock() }
        
        let info = profileCache[uid] ?? LNProfileUserInfo()
        info.uid = uid
        info.name = name
        info.avatar = avatar
        
        profileCache[uid] = info
        touchProfileCacheKey(uid)
        trimProfileCacheIfNeeded()
    }
    
    private func touchProfileCacheKey(_ uid: String) {
        if let index = profileCacheOrder.firstIndex(of: uid) {
            profileCacheOrder.remove(at: index)
        }
        profileCacheOrder.append(uid)
    }
    
    private func trimProfileCacheIfNeeded() {
        while profileCacheOrder.count > Self.profileCacheLimit {
            let expiredUid = profileCacheOrder.removeFirst()
            profileCache.removeValue(forKey: expiredUid)
        }
    }
}

extension LNProfileManager {
    func getBindPhoneCaptcha(code: String, phone: String,
                             queue: DispatchQueue = .main,
                             handler: @escaping (Bool) -> Void) {
        LNHttpManager.shared.getBindPhoneCaptcha(code: code, phone: phone) { [weak self] err in
            queue.asyncIfNotGlobal {
                handler(err == nil)
            }
            guard let self else { return }
            if let err {
                showToast(err.errorDesc)
            } else {
                captchaRemain = captchaCoolDown
                notifyCaptchaTime(time: captchaRemain)
                startCaptchaTimer()
            }
        }
    }
    
    func bindPhone(code: String, phone: String,
                   queue: DispatchQueue = .main,
                   captcha: String, handler: @escaping (Bool) -> Void) {
        LNHttpManager.shared.bindPhone(code: code, phone: phone, captcha: captcha) { err in
            queue.asyncIfNotGlobal {
                handler(err == nil)
            }
            if let err {
                showToast(err.errorDesc)
            }
        }
    }
    
    private func startCaptchaTimer() {
        runOnMain { [weak self] in
            guard let self else { return }
            stopCaptchaTimer()
            
            let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
                guard let self else { return }
                captchaRemain -= 1
                notifyCaptchaTime(time: captchaRemain)
                if captchaRemain == 0 {
                    stopCaptchaTimer()
                }
            }
            RunLoop.main.add(timer, forMode: .common)
            captchaTimer = timer
        }
    }
    
    private func stopCaptchaTimer() {
        captchaTimer?.invalidate()
        captchaTimer = nil
    }
}

extension LNProfileManager {
    func reportCurrentLanguage(code: String,
                               queue: DispatchQueue = .main,
                               handler: @escaping (Bool) -> Void) {
        LNHttpManager.shared.reportCurrentLanguage(code: code) { err in
            queue.asyncIfNotGlobal {
                handler(err == nil)
            }
        }
    }
}

extension LNProfileManager: LNAccountManagerNotify {
    func onUserLogin() {
        reloadMyProfileDetail()
    }
    
    func onUserLogout() {
        myUserInfo = LNUserProfileVO()
    }
}

extension LNProfileManager {
    private func notifyUserInfoChanged(newInfo: LNUserProfileVO) {
        LNEventDeliver.notifyEvent { ($0 as? LNProfileManagerNotify)?.onUserInfoChanged(userInfo: newInfo) }
    }
    
    private func notifyCaptchaTime(time: Int) {
        LNEventDeliver.notifyEvent {
            ($0 as? LNProfileManagerNotify)?.onBindPhoneCaptchaCoolDownChanged(time: time)
        }
    }
    
    private func notifyUserVoiceBarInfoChanged() {
        LNEventDeliver.notifyEvent { ($0 as? LNProfileManagerNotify)?.onUserVoiceBarInfoChanged() }
    }
}
