//
//  CloudStorage.swift

//
//  Created by Leon on 2025/7/21.
//

import AWSS3
import Foundation
import SwiftDate
// MARK: - CloudStorage

class CloudStorage {
    static let shared = CloudStorage()

    private(set) var uploadAlbumItems: [UploadPhotoM] = []
    private var uploadRoleItems: [UploadPhotoM] = []
    private var uploadHeadImagesItems: [UploadPhotoM] = []
    private var uploadIMImageItems: [UploadPhotoM] = []
    private var uploadAudiosItems: [UploadModel] = []
    
    var authAudioData: S3AuthData?
    var requestAudioSysTokenData: Date?
    var audioTransferUtility: AWSS3TransferUtility?
    
    private init() {
        NotificationCenter.default.addObserver(self, selector: #selector(receiveNetStatusChanged(_:)), name: AppNotificationName.networkChanged.notificationName, object: nil)
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    private func needCheckImage(for bucket: BucketS3Enum) -> Bool {
        switch bucket {
        case .HEAD_IMAGE: // , .ALBUM, .ROLE
            return true
        default:
            return false
        }
    }

    // MARK: - Public Methods

    func s3BatchAddPhotos(_ photos: [UploadPhotoM], bucket: BucketS3Enum, callback: ((Bool) -> Void)?) {
        guard !photos.isEmpty else {
            callback?(false)
            return
        }

        var uploadItems: [Any] = []
        switch bucket {
        case .HEAD_IMAGE:
            uploadItems = uploadHeadImagesItems
        case .ALBUM:
            uploadItems = uploadAlbumItems
        case .ROLE:
            uploadItems = uploadRoleItems
        case .IM_IMG:
            uploadItems = uploadIMImageItems
        case .SOUND, .SOUND_PATH:
            uploadItems = uploadAudiosItems
        default:
            callback?(false)
            return
        }

        for photo in photos {
            photo.isUploading = true
            photo.bucketEnum = bucket
            if !uploadItems.contains(where: { $0 as? UploadPhotoM === photo }) {
                uploadItems.append(photo)
            }
        }

        doS3UploadPhotos(bucket, photos: photos, block: callback)
    }

    func s3AddPhotos(_ photos: [UploadPhotoM], bucket: BucketS3Enum) {
        guard !photos.isEmpty else { return }

        var uploadItems: [Any] = []
        switch bucket {
        case .HEAD_IMAGE:
            uploadItems = uploadHeadImagesItems
        case .ALBUM:
            uploadItems = uploadAlbumItems
        case .ROLE:
            uploadItems = uploadRoleItems
        case .IM_IMG:
            uploadItems = uploadIMImageItems
        case .SOUND, .SOUND_PATH:
            uploadItems = uploadAudiosItems
        default:
            return
        }

        for photo in photos {
            photo.isUploading = true
            photo.bucketEnum = bucket
            if !uploadItems.contains(where: { $0 as? UploadPhotoM === photo }) {
                uploadItems.append(photo)
            }
        }

        doS3UploadPhotos(bucket, photos: photos, block: nil)
    }

    func s3AddUploadAudio(_ model: UploadModel, callback: ((Bool) -> Void)?) {
        guard let fileData = model.fileData, fileData.count > 0 else {
            assert(false, "fileData should be valid")
            callback?(false)
            return
        }

        model.isUploading = true
        if !uploadAudiosItems.contains(where: { $0 === model }) {
            uploadAudiosItems.append(model)
        }
        
        if judgeTokenValid(requestAudioSysTokenData){
            guard let authData = authAudioData, let utility = audioTransferUtility else{
                callback?(false)
                return
            }
            dlog("☁️利用之前的token上传Audio: \(model)")
            self.s3UploadModel(utility, object: model, auth: authData) { result in
                callback?(result)
            }
        }else{
            requestAuthToken(.SOUND_PATH, suffix: .mp3) { [weak self] result, authData, utility in
                guard let self = self, !self.uploadAudiosItems.isEmpty, let s3Utility = utility, let s3AuthData = authData else {
                    callback?(false)
                    return
                }
                
                if result {
                    self.requestAudioSysTokenData = Date()
                    self.s3UploadModel(s3Utility, object: model, auth: s3AuthData) { result in
                        callback?(result)
                    }
                } else {
                    model.isUploading = false
                    DispatchQueue.main.async {
                        model.mm_uploadFailed()
                    }
                    callback?(false)
                }
            }
        }
    }

    func cancelTasks(for bucket: BucketS3Enum) {
        switch bucket {
        case .ALBUM:
            for photo in uploadAlbumItems {
                cancelTask(for: photo)
                uploadAlbumItems.removeAll { $0 === photo }
            }
        case .HEAD_IMAGE:
            for photo in uploadHeadImagesItems {
                cancelTask(for: photo)
                uploadHeadImagesItems.removeAll { $0 === photo }
            }
        case .ROLE:
            for model in uploadRoleItems {
                cancelTask(for: model)
                uploadRoleItems.removeAll { $0 === model }
            }
        case .IM_IMG:
            for model in uploadIMImageItems {
                cancelTask(for: model)
                uploadIMImageItems.removeAll { $0 === model }
            }
        case .SOUND, .SOUND_PATH:
            for model in uploadAudiosItems{
                cancelTask(for: model)
                uploadAudiosItems.removeAll {$0 === model}
            }
        default:
            break
        }
    }

    // MARK: - Private Methods

    private func doS3UploadPhotos(_ bucket: BucketS3Enum, photos: [UploadPhotoM], block: ((Bool) -> Void)?) {
        guard !photos.isEmpty else {
            assert(false, "photos count 0")
            block?(false)
            return
        }

        let enableGCDGroup = block != nil
        var group: DispatchGroup?
        var isAllOKInGroup = true

        if enableGCDGroup {
            group = DispatchGroup()
        }

        var tempPhotos = photos
        // ⚠️
        // let checkImageNeedOfBucket = needCheckImage(for: bucket)

        for photo in photos {
            if let tempString = photo.remoteImageUrlString, tempString.count > 0 {
                tempPhotos.removeAll { $0 === photo }
                removeObjFromUploadingItems(by: photo)
                photo.mm_uploadDone(tempString)
                continue
            }

            guard photo.image != nil || photo.imageData != nil else {
                dlog("❌❌❌： 错误路径，图片应不能为空【CloudStorage】")
                continue
            }

            if enableGCDGroup {
                group?.enter()
            }

            if photo.imageData == nil {
                photo.imageData = convertImageToData(photo.image!, suffix: photo.suffixEnum)
            }

            let sizeInMB = Float(photo.imageData!.count) / (1000.0 * 1000.0)
            if sizeInMB > 10 {
                photo.setupPhotoOversizeLimmit()
                isAllOKInGroup = false
                DispatchQueue.main.async {
                    let message = photo.suffixEnum == .gif ? "Photo must be .JPG, .JPEG, .GIF or .PNG and cannot exceed 10MB" : "Photo must be .JPG, .JPEG or .PNG and cannot exceed 10MB"
                    photo.setupErrorMsg(message)
                    // Assuming Hud is a utility for showing alerts/toasts
                    // Unknown: Hud ShowPureTip implementation
                    dlog(message) // Placeholder for Hud.ShowPureTip
                    photo.mm_uploadFailed()
                }
                if enableGCDGroup {
                    group?.leave()
                }
                continue
            }

//            && checkImageNeedOfBucket
            let isCheckImageInCallback = block != nil  && photo.isAutoCheckImage
            if isCheckImageInCallback {
                photo.banCheckImageInDelegateMethod = true
            }

            let suffixType = photo.imageData != nil ? photo.suffixEnum : .jpeg

            requestAuthToken(bucket, suffix: suffixType) { [weak self] result, authData, utility in
                guard let self = self, let s3AuthData = authData, let s3Utility = utility else { return }
                if result {
                    self.s3Upload(s3Utility, photoM: photo, authData: s3AuthData) { success in
                        photo.isUploading = false
                        if success {
                            if isCheckImageInCallback {
                                photo.checkImageOK { result2 in
                                    if !result2 { // 鉴定失败❌
                                        isAllOKInGroup = false
                                    }
                                    if enableGCDGroup {
                                        group?.leave()
                                    }
                                }
                            } else {
                                // 不需要鉴黄，直接返回
                                if enableGCDGroup {
                                    group?.leave()
                                }
                            }
                        } else {
                            // 上传失败❌
                            isAllOKInGroup = false
                            if enableGCDGroup {
                                group?.leave()
                            }
                        }
                    }
                } else {
                    // 获取token失败❌
                    isAllOKInGroup = false
                    if enableGCDGroup {
                        group?.leave()
                    }
                    DispatchQueue.main.async {
                        photo.mm_uploadFailed()
                    }
                }
            }
        }

        if enableGCDGroup, let validGroup = group {
//            DispatchQueue.main.async(group: group) {
            validGroup.notify(queue: .main) {
                block?(isAllOKInGroup)
            }
        }
    }

    private func s3Upload(_ utility: AWSS3TransferUtility, photoM im: UploadPhotoM, authData: S3AuthData, result: @escaping (Bool) -> Void) {
        guard !authData.path.isEmpty else {
            DispatchQueue.main.async {
                dlog("☁️❌authData.path is empty")
                im.mm_uploadFailed()
                result(false)
            }
            return
        }

        if LTNetworkManage.ltManage.reachable == false { // Placeholder for [EGApiManager shared].status == .notReachable
            DispatchQueue.main.async {
                im.mm_uploadFailed()
                result(false)
            }
            return
        }

        im.s3AuthData = authData
        im.utility = utility

        let key = authData.path
        dlog("☁️uplading key: \(key)")

        let expression = AWSS3TransferUtilityUploadExpression()
        expression.progressBlock = { _, progress in
            dlog("🚀 \(key) taskprogress: \(progress)")
        }
        expression.setValue("temp=1", forRequestHeader: "x-amz-tagging")

        guard let imageData = convertImageToData(im.image!, suffix: im.suffixEnum) ?? im.imageData else { return }
        let contentType = im.contentType()

        utility.uploadData(imageData, bucket: authData.bucket, key: key, contentType: contentType, expression: expression) { task, error in
            self.removeObjFromUploadingItems(by: im)
            if let error = error {
                dlog("❌S3上传任务失败: \(error.localizedDescription)")
                DispatchQueue.main.async {
                    im.mm_uploadFailed()
                    result(false)
                }
                return
            }

            switch task.status {
            case .completed:
                im.remoteFullPath = authData.urlPath
                im.remoteImageUrlString = key
                dlog("💹 s3 image uploaded full: \(im.remoteFullPath ?? "")")
                DispatchQueue.main.async {
                    im.mm_uploadDone(key)
                    result(true)
                }
            case .error, .cancelled:
                DispatchQueue.main.async {
                    im.mm_uploadFailed()
                    result(false)
                }
            default:
                assert(false, "上传异常分支、需要查查原因")
            }
        }
    }

    private func s3UploadModel(_ utility: AWSS3TransferUtility, object model: UploadModel, auth: S3AuthData, result: @escaping (Bool) -> Void) {
        guard !auth.path.isEmpty, let fileData = model.fileData else {
            DispatchQueue.main.async {
                model.mm_uploadFailed()
            }
            result(false)
            return
        }

        // Assuming EGApiManager is a network status manager
        // Unknown: EGApiManager shared status implementation
        if false { // Placeholder for [EGApiManager shared].status == .notReachable
            DispatchQueue.main.async {
                model.mm_uploadFailed()
                result(false)
            }
            return
        }

        model.s3AuthData = auth
        model.utility = utility

        var key = auth.path
        /// dev/main/sound/0/*
        if key.hasSuffix("/*") {
            // 移除最后的 "/*"
            key.removeLast(2)
            // 拼接时间戳 + .mp3
            key += "/\(model.addThisItemTimeStamp).mp3"
        }
        
        var urlPath = auth.urlPath ?? ""
        if  urlPath.hasSuffix("/*") {
            // 移除最后的 "/*"
            urlPath.removeLast(2)
            // 拼接时间戳 + .mp3
            urlPath += "/\(model.addThisItemTimeStamp).mp3"
        }
        dlog("☁️uplading file key: \(key) full: \(urlPath)")

        let expression = AWSS3TransferUtilityUploadExpression()
        expression.progressBlock = { _, progress in
            dlog("🚀 \(key) file taskprogress: \(progress)")
        }
        expression.setValue("temp=1", forRequestHeader: "x-amz-tagging")

        let contentType = model.contentType()

        utility.uploadData(fileData, bucket: auth.bucket, key: key, contentType: contentType, expression: expression) { task, error in
            self.uploadAudiosItems.removeAll { $0 === model }
            if let error = error {
                dlog("❌S3上传任务失败: \(error.localizedDescription)")
                result(false)
                return
            }

            switch task.status {
            case .completed:
                model.remoteFullPath = urlPath // auth.urlPath
                model.remoteImageUrlString = key
                dlog("💹 s3 file uploaded full: \(model.remoteFullPath ?? "")")
                DispatchQueue.main.async {
                    model.mm_uploadDone(key)
                    result(true)
                }
            case .error, .cancelled:
                DispatchQueue.main.async {
                    model.mm_uploadFailed()
                    result(false)
                }
            default:
                assert(false, "上传异常分支、需要查查原因")
            }
        }
    }

    private func requestAuthToken(_ bucket: BucketS3Enum, suffix: SuffixS3Enum, callback: @escaping (Bool, S3AuthData?, AWSS3TransferUtility?) -> Void) {
        OssProvider.request(.getS3Token(bucketNameEnum: bucket, suffix: suffix), modelType: S3AuthData.self) {[weak self] result in
            switch result {
            case let .success(success):
                if let authData = success {
                    let utility = self?.generateTransferUtility(bucket, authData: authData)
                    callback(true, authData, utility)
                    
                    if bucket == .SOUND_PATH{
                        self?.authAudioData = success
                    }
                } else {
                    callback(false, nil, nil)
                }
            case .failure:
                callback(false, nil, nil)
            }
        }
    }

    private func generateTransferUtility(_ bucket: BucketS3Enum, authData: S3AuthData) -> AWSS3TransferUtility? {
        guard let accessKeyId = authData.accessKeyId, !accessKeyId.isEmpty,
              let accessKeySecret = authData.accessKeySecret, !accessKeySecret.isEmpty,
              let securityToken = authData.securityToken, !securityToken.isEmpty  else {
            assert(false, "invalid token")
            return nil
        }

        /// Region(⚠️String) from server
        // let region = authData.region
        
        let provider = AWSBasicSessionCredentialsProvider(accessKey: accessKeyId, secretKey: accessKeySecret, sessionToken: securityToken)
        // ⚠️（Integer）支持哪些region，需要转化。目前强制 .USWest2
        let regionType: AWSRegionType = .USWest2;
        guard let config = AWSServiceConfiguration(region: regionType, credentialsProvider: provider) else {
            assert(false, "Invalid AWSServiceConfiguration")
            return nil
        }
        config.timeoutIntervalForRequest = 30
        config.maxRetryCount = 1

        let key = authData.fileName
        AWSS3TransferUtility.remove(forKey: key) //        AWSS3TransferUtility.removeS3TransferUtility(forKey: key)
        AWSS3TransferUtility.register(with: config, forKey: key)

        let utility = AWSS3TransferUtility.s3TransferUtility(forKey: key) // AWSS3TransferUtility(forKey: key)
        if utility == nil {
            assert(false, "invalid utility")
        }
        
        switch bucket{
        case .SOUND_PATH:
            audioTransferUtility = utility
        default:
            break
        }
        
        return utility
    }

    private func judgeTokenValid(_ lastRequestDate: Date?) -> Bool {
        guard let date = lastRequestDate else {
            return false
        }
        let expireDate = date + 59.minutes

        if expireDate.milliStamp > Date().milliStamp {
            return true
        }
        return false
    }

    private func removeObjFromUploadingItems(by obj: UploadPhotoM) {
        uploadAlbumItems.removeAll { $0 === obj }
        uploadHeadImagesItems.removeAll { $0 === obj }
        uploadRoleItems.removeAll { $0 === obj }
        uploadAudiosItems.removeAll { $0 === obj }
    }

    private func cancelTask<T: UploadEntity>(for item: T) {
        // Unknown: AWSTask and AWSS3TransferUtilityUploadTask implementation
        if let utility = item.utility {
            // Placeholder for task cancellation
            dlog("🈲🈲, upload task canceled : \(utility)")
            DispatchQueue.main.async {
                item.mm_uploadFailed()
            }
        }
    }

    // MARK: - Notification Handling

    @objc private func receiveNetStatusChanged(_ notification: Notification) {
        let status = LTNetworkManage.ltManage.status
        if status == .notReachable {
            cancelTasks(for: .ALBUM)
            cancelTasks(for: .HEAD_IMAGE)
            cancelTasks(for: .ROLE)
            cancelTasks(for: .SOUND)
            cancelTasks(for: .IM_IMG)
        }
    }

    // MARK: - Helper Methods

    private func convertImageToData(_ image: UIImage, suffix: SuffixS3Enum) -> Data? {
        // Unknown: SDWebImage or UIImage conversion implementation
        switch suffix {
        case .gif:
            assert(false, "imageData应该不为空")
            return nil
        // return image.sd_imageData(as: .gif) // Placeholder
        case .png:
            return image.pngData()
        default:
            return image.jpegData(compressionQuality: 0.8)
        }
    }
}
