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

import Foundation
import GoogleSignIn
import AuthenticationServices


protocol LNAccountManagerNotify {
    func onUserLogin()
    func onUserLogout()
    
    func onLoginCaptchaCoolDownChanged(time: Int)
}
extension LNAccountManagerNotify {
    func onUserLogin() {}
    func onUserLogout() {}
    
    func onLoginCaptchaCoolDownChanged(time: Int) { }
}

extension String {
    var isMyUid: Bool {
        myUid == self
    }
}

var myUid: String {
    LNAccountManager.shared.uid
}

var hasLogin: Bool {
    !myUid.isEmpty
}

class LNAccountManager: NSObject {
    static let shared = LNAccountManager()
    
    private let onlineHeartbeatInterval: TimeInterval = 5
    private var onlineHeartbeatTimer: Timer?
    private var isReportingOnlineHeartbeat = false
    
    private(set) var token = LNUserDefaults[.token, ""] {
        didSet { LNUserDefaults[.token] = token }
    }
    private(set) var uid: String = LNUserDefaults[.uid, ""] {
        didSet { LNUserDefaults[.uid] = uid }
    }
    
    var wasLogin: Bool {
        !token.isEmpty && !uid.isEmpty
    }
    
    private let captchaCoolDown = 60
    private var captchaRemain = 0
    private var captchaTimer: Timer?
    var canSendCaptcha: Bool {
        captchaRemain == 0
    }
    
    func doGoogleLogin(_ vc: UIViewController) {
        showLoading()
        GIDSignIn.sharedInstance.signIn(withPresenting: vc) { [weak self] result, err in
            guard let self else { return }
            guard err == nil,
                  let result,
                  let token = result.user.idToken?.tokenString else {
                dismissLoading()
                return
            }
            self.loginByGoogle(data: token) { _ in
                dismissLoading()
            }
        }
    }
    
    func doAppleLogin() {
        let provider = ASAuthorizationAppleIDProvider()
        let request = provider.createRequest()
        request.requestedScopes = [.fullName, .email]
        let controller = ASAuthorizationController(authorizationRequests: [request])
        controller.delegate = self
//            controller.presentationContextProvider = self
        showLoading()
        controller.performRequests()
    }
    
    private override init() {
        super.init()
        
        let clientID = if LNAppConfig.shared.curEnv == .test {
            "981655295954-noc65ii1gfgpq3mrc0r75t7gq66v57bj.apps.googleusercontent.com"
        } else {
            "955524882346-a7fs1l3798khu5hn058m0veqqcvli7h4.apps.googleusercontent.com"
        }
        
        GIDSignIn.sharedInstance.configuration = GIDConfiguration(clientID: clientID)
    }
}

extension LNAccountManager: ASAuthorizationControllerDelegate {
    func authorizationController(controller: ASAuthorizationController,
                                 didCompleteWithAuthorization authorization: ASAuthorization) {
        guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential,
              let token = credential.identityToken,
              let tokenStr = String(data: token, encoding: .utf8)
        else {
            dismissLoading()
            return
        }
        
        loginByApple(data: tokenStr) { _ in
            dismissLoading()
        }
    }
    
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: any Error) {
        dismissLoading()
    }
}

extension LNAccountManager {
    func loginByToken(handler: ((Bool) -> Void)? = nil) {
        LNHttpManager.shared.refreshToken { [weak self] res, err in
            guard let self else { return }
            guard err == nil, let res else {
                if case .serverError = err {
                    self.clean()
                }
                showToast(err?.errorDesc)
                handler?(false)
                return
            }
            self.token = res.token
            handler?(true)
            
            self.notifyUserLogin()
        }
    }
    
    private func loginByGoogle(data: String, handler: ((Bool) -> Void)? = nil) {
        LNHttpManager.shared.loginByGoogle(token: data) { [weak self] response, err in
            guard let self else { return }
            guard err == nil, let response else {
                showToast(err?.errorDesc)
                handler?(false)
                self.clean()
                return
            }
            self.token = response.token
            self.uid = response.userProfile.userNo
            handler?(true)
            
            self.notifyUserLogin()
            LNStatisticManager.shared.reportRegister(method: .google)
        }
    }
    
    private func loginByApple(data: String, handler: ((Bool) -> Void)? = nil) {
        LNHttpManager.shared.loginByApple(token: data) { [weak self] response, err in
            guard let self else { return }
            guard err == nil, let response else {
                showToast(err?.errorDesc)
                handler?(false)
                self.clean()
                return
            }
            self.token = response.token
            self.uid = response.userProfile.userNo
            handler?(true)
            
            self.notifyUserLogin()
            LNStatisticManager.shared.reportRegister(method: .apple)
        }
    }
    
    func loginByPhone(code: String, num: String, captcha: String,
                      handler: ((Bool) -> Void)? = nil) {
        LNHttpManager.shared.loginByPhone(code: code, num: num, captcha: captcha)
        { [weak self] response, err in
            guard let self else { return }
            guard err == nil, let response else {
                showToast(err?.errorDesc)
                handler?(false)
                self.clean()
                return
            }
            self.token = response.token
            self.uid = response.userProfile.userNo
            handler?(true)
            
            self.notifyUserLogin()
            LNStatisticManager.shared.reportRegister(method: .phone)
        }
    }
    
    func logout() {
        LNHttpManager.shared.logout { [weak self] err in
            guard let self else { return }
            guard err == nil else {
                return
            }
            self.clean()
        }
    }
    
#if DEBUG
    func loginByEmail(email: String, completion: @escaping (Bool) -> Void) {
        LNHttpManager.shared.loginByEmail(email: email) { [weak self] response, err in
            guard let self else { return }
            guard err == nil, let response else {
                showToast(err?.errorDesc)
                completion(false)
                self.clean()
                return
            }
            self.token = response.token
            self.uid = response.userProfile.userNo
            completion(true)
            
            self.notifyUserLogin()
        }
    }
#endif
}

extension LNAccountManager {
    func getLoginCaptcha(code: String, phone: String,
                         queue: DispatchQueue = .main,
                         handler: @escaping (Bool) -> Void) {
        LNHttpManager.shared.getLoginCaptcha(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()
            }
        }
    }
    
    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 LNAccountManager {
    func clean() {
        let wasLogin = !token.isEmpty
        token = ""
        uid = ""
        
        if wasLogin {
            notifyUserLogout()
        }
    }
}

extension LNAccountManager {
    private func startOnlineHeartbeatTimerIfNeed() {
        runOnMain { [weak self] in
            guard let self else { return }
            guard onlineHeartbeatTimer == nil else { return }
            let timer = Timer.scheduledTimer(withTimeInterval: onlineHeartbeatInterval, repeats: true)
            { [weak self] _ in
                guard let self else { return }
                guard wasLogin && LNAppConfig.shared.isForeground else {
                    return
                }
                isReportingOnlineHeartbeat = true
                LNHttpManager.shared.reportOnlineHeartbeat { [weak self] err in
                    guard let self else { return }
                    isReportingOnlineHeartbeat = false
                    if let err {
                        Log.d("report online heartbeat failed: \(err.errorDesc)")
                    }
                }
            }
            RunLoop.main.add(timer, forMode: .common)
            onlineHeartbeatTimer = timer
            Log.d("start online heartbeat")
        }
    }
    
    private func stopOnlineHeartbeatTimer() {
        runOnMain { [weak self] in
            guard let self else { return }
            onlineHeartbeatTimer?.invalidate()
            onlineHeartbeatTimer = nil
            Log.d("stop online heartbeat")
        }
    }
}

extension LNAccountManager {
    private func notifyUserLogin() {
        startOnlineHeartbeatTimerIfNeed()
        LNEventDeliver.notifyEvent { ($0 as? LNAccountManagerNotify)?.onUserLogin() }
    }

    private func notifyUserLogout() {
        stopOnlineHeartbeatTimer()
        LNEventDeliver.notifyEvent { ($0 as? LNAccountManagerNotify)?.onUserLogout() }
    }
    
    private func notifyCaptchaTime(time: Int) {
        LNEventDeliver.notifyEvent {
            ($0 as? LNAccountManagerNotify)?.onLoginCaptchaCoolDownChanged(time: time)
        }
    }
}
