//
//  YHOOSManager.swift
//  galaxy
//
//  Created by EDY on 2024/1/16.
//  Copyright © 2024 https://www.galaxy-immi.com. All rights reserved.
//

import UIKit

public protocol Error {
    var _domain: String { get }
    var _code: Int { get }
    var _userInfo: AnyObject? { get }
}

extension NSError : Error {
    @nonobjc
    public var _domain: String { return domain }
    
    @nonobjc
    public var _code: Int { return code }
    
    @nonobjc
    public var _userInfo: AnyObject? { return userInfo as NSDictionary }
}

class YHOOSDefine {
    static let OSS_ACCESSKEY_ID: String = "access_key_id"
    static let OSS_SECRETKEY_ID: String = "access_key_secret"
    static let OSS_BUCKET_PUBLIC: String = "public-bucket"
    static let OSS_BUCKET_PRIVATE: String = "private-bucket"
    static let OSS_ENDPOINT: String = "http://oss-cn-region.aliyuncs.com"
    static let OSS_MULTIPART_UPLOADKEY: String = "multipart"
    static let OSS_RESUMABLE_UPLOADKEY: String = "resumable"
    static let OSS_CALLBACK_URL: String = "http://oss-demo.aliyuncs.com:23450"
    static let OSS_CNAME_URL: String = "http://www.cnametest.com/"
    static let OSS_STSTOKEN_URL: String = "http://*.*.*.*.****/sts/getsts"
    static let OSS_IMAGE_KEY: String = "testImage.png"
    static let OSS_CRC64_ENABLE: Bool = true
    static let OSS_CONTENT_TYPE: String = "Content-Type"
    static let OSS_CONTENT_LENGTH: String = "Content-Length"
    static let OSS_TEST_CONTENT_TYPE: String = "application/special"
    static let OSS_APPEND_OBJECT_KEY: String = "appendObject"

    static let SCHEME = "https://"
    static let ENDPOINT = "oss-cn-hangzhou.aliyuncs.com"
    static let CNAME_ENDPOINT = "oss.custom.com"
    static let IP_ENDPOINT = "192.168.1.1:8080"
    static let BUCKET_NAME = "BucketName"
    static let OBJECT_KEY = "ObjectKey"
}

class YHOOSManager: NSObject, URLSessionDelegate {
    static let share = YHOOSManager()
    var mProvider: OSSAuthCredentialProvider!
    var mClient: OSSClient!

    override init() {
        mProvider = OSSAuthCredentialProvider(authServerUrl: YHOOSDefine.OSS_STSTOKEN_URL)
        mClient = OSSClient(endpoint: YHOOSDefine.OSS_ENDPOINT, credentialProvider: mProvider)
    }
    
    func getObject() {
        let getObjectReq: OSSGetObjectRequest = OSSGetObjectRequest()
        getObjectReq.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE
        getObjectReq.objectKey = YHOOSDefine.OSS_IMAGE_KEY
        getObjectReq.downloadProgress = { (bytesWritten: Int64,totalBytesWritten : Int64, totalBytesExpectedToWrite: Int64) -> Void in
            printLog("bytesWritten:\(bytesWritten),totalBytesWritten:\(totalBytesWritten),totalBytesExpectedToWrite:\(totalBytesExpectedToWrite)")
        };
        let task: OSSTask = mClient.getObject(getObjectReq);
        task.continue({(t) -> OSSTask<AnyObject>? in
            self.showResult(task: t)
            return nil
        })
        task.waitUntilFinished()
        
        printLog("Error:\(String(describing: task.error))")
    }
    
    func getImage() {
        let getObjectReq: OSSGetObjectRequest = OSSGetObjectRequest()
        getObjectReq.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE
        getObjectReq.objectKey = "ImageKey"
        getObjectReq.xOssProcess = "image/resize,m_lfit,w_100,h_100"
        getObjectReq.downloadProgress = { (bytesWritten: Int64,totalBytesWritten : Int64, totalBytesExpectedToWrite: Int64) -> Void in
            printLog("bytesWritten:\(bytesWritten),totalBytesWritten:\(totalBytesWritten),totalBytesExpectedToWrite:\(totalBytesExpectedToWrite)")
        };
        let task: OSSTask = mClient.getObject(getObjectReq);
        task.continue({(t) -> OSSTask<AnyObject>? in
            self.showResult(task: t)
            return nil
        })
        task.waitUntilFinished()
        
        printLog("Error:\(String(describing: task.error))")
    }
    
    func getStsToken() {
        let tcs = OSSTaskCompletionSource<AnyObject>()
        let federationProvider: OSSFederationCredentialProvider = OSSFederationCredentialProvider(federationTokenGetter: {() ->OSSFederationToken? in
            let url: URL = URL(string: YHOOSDefine.OSS_STSTOKEN_URL)!
            let config: URLSessionConfiguration = URLSessionConfiguration.default
            let session: URLSession = URLSession(configuration: config, delegate: self as URLSessionDelegate, delegateQueue: nil)
            
            let task = session.dataTask(with: url, completionHandler: { (data, response, error) -> Void in
                
                //Convert Data to Jsons
                tcs.setResult(data as AnyObject)
            })
            task.resume()
            tcs.task.waitUntilFinished()
            
            let json = try? JSONSerialization.jsonObject(with: tcs.task.result as! Data,
                                                         options:.allowFragments) as? [String: Any]
            print("Json Object:", json as Any)
            
            //verify json
            let accessKeyId = json?["AccessKeyId"]
            let accessKeySecret = json?["AccessKeySecret"]
            
            self.ossAlert(title: "notice", message: json?.description)
            
            
            let token = OSSFederationToken()
            token.tAccessKey = accessKeyId as! String
            token.tSecretKey = accessKeySecret as! String
            
            return token
        })
        
        do {
            try federationProvider.getToken()
        } catch{
            print("get Error")
        }
    }
    
    func headObject() {
        let objectKey = "key"
        if objectKey.isEmpty {
            ossAlert(title: nil, message: "objectKey can not be empty!")
        }
        
        let request = OSSHeadObjectRequest()
        request.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE
        request.objectKey = objectKey
    
        let task: OSSTask = mClient.headObject(request)
        task.continue({(task) -> OSSTask<AnyObject>? in
            self.showResult(task: task)
            return nil
        })
        task.waitUntilFinished()
    }
    
    func getBucket() {
        let request = OSSGetBucketRequest()
        request.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE
        
        let task = mClient.getBucket(request)
        task.continue( { (t) -> Any? in
            if let result = t.result as? OSSGetBucketResult {
                self.showResult(task: OSSTask(result: result.contents as AnyObject))
            } else {
                self.showResult(task: t)
            }
            return nil
        })
    }
    
    func getBucketACL() -> Void {
        let request = OSSGetBucketACLRequest()
        request.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE
        
        let task = mClient.getBucketACL(request)
        task.continue( { (t) -> Any? in
            if let result = t.result as? OSSGetBucketACLResult {
                self.showResult(task: OSSTask(result: result.aclGranted as AnyObject))
            } else {
                self.showResult(task: t)
            }
            return nil
        })
    }
    
    func createBucket() -> Void {
        let request = OSSCreateBucketRequest()
        request.bucketName = "com-dhc-test"
        
        let task = mClient.createBucket(request)
        task.continue( { (t) -> Any? in
            self.showResult(task: t)
            return nil
        })
    }
    
    func deleteBucket() -> Void {
        let request = OSSDeleteBucketRequest()
        request.bucketName = "com-dhc-test"
        
        let task = mClient.deleteBucket(request)
        task.continue( { (t) -> Any? in
            self.showResult(task: t)
            return nil
        })
    }
    
    func putObject(image: UIImage) {
        let request = OSSPutObjectRequest()
        request.uploadingData = image.pngData()!
        request.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE
        request.objectKey = "landscape-painting.jpeg"
        request.uploadProgress = { (bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) -> Void in
            printLog("bytesSent:\(bytesSent),totalBytesSent:\(totalBytesSent),totalBytesExpectedToSend:\(totalBytesExpectedToSend)")
        };
        
        let provider = OSSFederationTokenCredentialProvider {
            let tcs = TaskCompletionSource()
            DispatchQueue(label: "test").async {
                Thread.sleep(forTimeInterval: 10)
                let token = OSSFederationToken()
                token.tAccessKey = "STS.tAccessKey"
                token.tSecretKey = "tSecretKey"
                token.tToken = "tToken"
                token.expirationTimeInGMTFormat = "2023-05-23T07:34:27Z"
                // or tcs.trySetError(<#T##error: Error##Error#>)
                tcs.trySetResult(token)
            }
            tcs.wait(timeout: 5)
            if let error = tcs.task.error {
                let nsError = error as NSError
                if nsError.code == OSSClientErrorCODE.codeNotKnown.rawValue,
                   let errorMessage = nsError.userInfo[OSSErrorMessageTOKEN] as? String,
                   errorMessage == "TaskCompletionSource wait timeout." {
                    // 超时错误
                }
                throw error
            } else if let result = tcs.task.result as? OSSFederationToken {
                return result
            }
            throw NSError(domain: OSSClientErrorDomain,
                          code: OSSClientErrorCODE.codeSignFailed.rawValue,
                          userInfo: [OSSErrorMessageTOKEN : "Can not get FederationToken."])
        }
        
        let client = OSSClient(endpoint: YHOOSDefine.OSS_ENDPOINT, credentialProvider: provider)
        let task = client.putObject(request)
        task.continue({ (t) -> Any? in
            self.showResult(task: t)
        }).waitUntilFinished()
    }
    
    func multipartUpload() {
        let request = OSSMultipartUploadRequest()
        request.uploadingFileURL = Bundle.main.url(forResource: "wangwang", withExtension: "zip")!
        request.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE
        request.objectKey = "wangwang(swift).zip"
        request.partSize = 102400;
        request.uploadProgress = { (bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) -> Void in
            printLog("bytesSent:\(bytesSent),totalBytesSent:\(totalBytesSent),totalBytesExpectedToSend:\(totalBytesExpectedToSend)")
        };
        
        let provider = OSSAuthCredentialProvider(authServerUrl: YHOOSDefine.OSS_STSTOKEN_URL)
        let client = OSSClient(endpoint: YHOOSDefine.OSS_ENDPOINT, credentialProvider: provider)
        let task = client.multipartUpload(request)
        task.continue({ (t) -> Any? in
            self.showResult(task: t)
        }).waitUntilFinished()
    }
    
    func sequentialMultipartUpload() {
        let request = OSSResumableUploadRequest()
        request.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE;
        request.objectKey = "sequential-swift-multipart"
        request.uploadingFileURL = Bundle.main.url(forResource: "wangwang", withExtension: "zip")!
        request.deleteUploadIdOnCancelling = false
        request.crcFlag = OSSRequestCRCFlag.open
        let filePath = Bundle.main.path(forResource: "wangwang", ofType: "zip")
        request.contentSHA1 = OSSUtil.sha1(withFilePath: filePath)
        
        let task = mClient.sequentialMultipartUpload(request)
        task.continue({ (t) -> Any? in
            self.showResult(task: t)
            return nil
        }).waitUntilFinished()
    }
    
    func resumableUpload() {
        var request = OSSResumableUploadRequest()
        request.uploadingFileURL = Bundle.main.url(forResource: "wangwang", withExtension: "zip")!
        request.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE
        request.deleteUploadIdOnCancelling = false
        request.objectKey = "wangwang(swift).zip"
        let cacheDir =  NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
        request.recordDirectoryPath = cacheDir!
        request.partSize = 102400
        request.uploadProgress = { (bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) -> Void in
            printLog("bytesSent:\(bytesSent),totalBytesSent:\(totalBytesSent),totalBytesExpectedToSend:\(totalBytesExpectedToSend)")
            if totalBytesSent > (totalBytesExpectedToSend / 2) {
                request.cancel()
            }
        }
        
        var task = mClient.resumableUpload(request)
        task.continue({ (t) -> Any? in
            print("Error: \(String(describing: t.error))")
            return nil
        }).waitUntilFinished()
        
        request = OSSResumableUploadRequest()
        request.uploadingFileURL = Bundle.main.url(forResource: "wangwang", withExtension: "zip")!
        request.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE
        request.objectKey = "wangwang(swift).zip"
        request.partSize = 102400;
        request.deleteUploadIdOnCancelling = false
        request.recordDirectoryPath = cacheDir!
        request.uploadProgress = { (bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) -> Void in
            printLog("bytesSent:\(bytesSent),totalBytesSent:\(totalBytesSent),totalBytesExpectedToSend:\(totalBytesExpectedToSend)")
        }
        
        task = mClient.resumableUpload(request)
        task.continue({ (t) -> Any? in
            self.showResult(task: t)
            return nil
        }).waitUntilFinished()
    }
    
    func putObjectWithXML() {
        let request = OSSPutObjectRequest()
        request.uploadingFileURL = Bundle.main.url(forResource: "test", withExtension: "xml")!
        request.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE
        request.objectKey = "test.xml"
        request.contentSHA1 = OSSUtil.sha1(withFilePath: Bundle.main.path(forResource: "test", ofType: "xml"))
        
        request.uploadProgress = { (bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) -> Void in
            print("bytesSent:\(bytesSent),totalBytesSent:\(totalBytesSent),totalBytesExpectedToSend:\(totalBytesExpectedToSend)");
        };
        
        let task = mClient.putObject(request)
        task.continue({ (t) -> Any? in
            self.showResult(task: t)
        }).waitUntilFinished()
    }
    
    func getObjectACL() {
        let request = OSSGetObjectACLRequest()
        request.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE
        request.objectName = YHOOSDefine.OSS_IMAGE_KEY
        
        let task = mClient.getObjectACL(request)
        task.continue({ (t) -> Any? in
            self.showResult(task: t)
            
            return nil
        }).waitUntilFinished()
    }
    
    func deleteMultipleObjects() {
        let request = OSSDeleteMultipleObjectsRequest()
        request.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE
        request.keys = ["testFor5m"]
        request.quiet = false
        request.encodingType = "url"
        
        let task = mClient.deleteMultipleObjects(request)
        task.continue({ (t) -> Any? in
            self.showResult(task: t)
            
            return nil
        }).waitUntilFinished()
    }
    
    func triggerCallBack() {
        let provider = OSSPlainTextAKSKPairCredentialProvider.init(plainTextAccessKey: "AK", secretKey: "SK")
        let pClient = OSSClient.init(endpoint: YHOOSDefine.OSS_ENDPOINT, credentialProvider: provider)
        
        let request = OSSCallBackRequest()
        request.bucketName = YHOOSDefine.OSS_BUCKET_PRIVATE
        request.objectName = "file1m"
        request.callbackVar = ["key1": "value1",
                               "key2": "value2"]
        request.callbackParam = ["callbackUrl": YHOOSDefine.OSS_CALLBACK_URL,
                                "callbackBody": "test"]
        
        let task = pClient.triggerCallBack(request)
        task.continue({ (t) -> Any? in
            if (t.result != nil) {
                let result = t.result as! OSSCallBackResult;
                self .ossAlert(title: "提示", message: result.serverReturnJsonString);
            }
            
            return nil
        }).waitUntilFinished()
    }
    
    //验证结果
    func showResult(task: OSSTask<AnyObject>?) {
        if (task?.error != nil) {
            let error: NSError = (task?.error)! as NSError
            self.ossAlert(title: "error", message: error.description)
        } else {
            let result = task?.result
            self.ossAlert(title: "notice", message: result?.description)
        }
    }
    
    func ossAlert(title: String?,message:String?) {
        DispatchQueue.main.async {
            let alertCtrl = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
            alertCtrl.addAction(UIAlertAction(title: "confirm", style: UIAlertAction.Style.default, handler: { (action) in
                print("\(action.title!) has been clicked");
                alertCtrl.dismiss(animated: true, completion: nil)
            }))
            UIViewController.current?.present(alertCtrl, animated: true, completion: nil)
        }
    }
}

public class OSSFederationTokenCredentialProvider: OSSFederationCredentialProvider {
    var token: OSSFederationToken?
    private var tokenGetter: () throws -> OSSFederationToken
    
    public init(tokenGetter: @escaping () throws -> OSSFederationToken) {
        self.tokenGetter = tokenGetter
        super.init()
    }
    
    public override func getToken() throws -> OSSFederationToken {
        do {
            if var token = token {
                if let expirationTimeInGMTFormat = token.expirationTimeInGMTFormat {
                    let dateFormatter = DateFormatter()
                    dateFormatter.timeZone = TimeZone(identifier: "GMT")
                    dateFormatter.locale = Locale(identifier: "en_US_POSIX")
                    dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
                    if let data = dateFormatter.date(from: expirationTimeInGMTFormat) {
                        token.expirationTimeInMilliSecond = Int64(data.timeIntervalSince1970 * 1000)
                    }
                }
                let expirationDate = Date(timeIntervalSince1970: TimeInterval(token.expirationTimeInMilliSecond / 1000))
                let interval = expirationDate.timeIntervalSince(NSDate.oss_clockSkewFixed())
                if interval < 5 * 60 {
                    token = try self.tokenGetter()
                    self.token = token
                }
                return token
            } else {
                let token = try self.tokenGetter()
                self.token = token
                return token
            }
        } catch {
            throw NSError(domain: OSSClientErrorDomain,
                          code: OSSClientErrorCODE.codeSignFailed.rawValue,
                          userInfo: [OSSErrorMessageTOKEN : error])
        }
    }
}

public class TaskCompletionSource: OSSTaskCompletionSource<AnyObject> {
    
    public func wait(timeout: TimeInterval) {
        let timer = DispatchSource.makeTimerSource()
        timer.schedule(deadline: .now() + timeout)
        timer.setEventHandler {
            if !self.task.isCompleted {
                let error = NSError(domain: OSSClientErrorDomain,
                                    code: OSSClientErrorCODE.codeNotKnown.rawValue,
                                    userInfo: [OSSErrorMessageTOKEN : "TaskCompletionSource wait timeout."])
                self.trySetError(error)
            }
        }
        timer.resume()
        task.waitUntilFinished()
        timer.cancel()
    }
}
