import AVFoundation
import Promises

enum RCTVideoDRM {
    static func fetchLicense(
        licenseServer: String,
        spcData: Data?,
        contentId: String,
        headers: [String: Any]?
    ) -> Promise<Data> {
        let request = createLicenseRequest(licenseServer: licenseServer, spcData: spcData, contentId: contentId, headers: headers)

        return Promise<Data>(on: .global()) { fulfill, reject in
            let postDataTask = URLSession.shared.dataTask(
                with: request as URLRequest,
                completionHandler: { (data: Data!, response: URLResponse!, error: Error!) in
                    let httpResponse: HTTPURLResponse! = (response as! HTTPURLResponse)

                    guard error == nil else {
                        print("Error getting license from \(licenseServer), HTTP status code \(httpResponse.statusCode)")
                        reject(error)
                        return
                    }
                    guard httpResponse.statusCode == 200 else {
                        print("Error getting license from \(licenseServer), HTTP status code \(httpResponse.statusCode)")
                        reject(RCTVideoErrorHandler.licenseRequestNotOk(httpResponse.statusCode))
                        return
                    }

                    guard data != nil, let decodedData = Data(base64Encoded: data, options: []) else {
                        reject(RCTVideoErrorHandler.noDataFromLicenseRequest)
                        return
                    }

                    fulfill(decodedData)
                }
            )
            postDataTask.resume()
        }
    }

    static func createLicenseRequest(
        licenseServer: String,
        spcData: Data?,
        contentId: String,
        headers: [String: Any]?
    ) -> URLRequest {
        var request = URLRequest(url: URL(string: licenseServer)!)
        request.httpMethod = "POST"

        if let headers {
            for item in headers {
                guard let key = item.key as? String, let value = item.value as? String else {
                    continue
                }
                request.setValue(value, forHTTPHeaderField: key)
            }
        }

        let spcEncoded = spcData?.base64EncodedString(options: [])
        let spcUrlEncoded = CFURLCreateStringByAddingPercentEscapes(
            kCFAllocatorDefault,
            spcEncoded as? CFString? as! CFString,
            nil,
            "?=&+" as CFString,
            CFStringBuiltInEncodings.UTF8.rawValue
        ) as? String
        let post = String(format: "spc=%@&%@", spcUrlEncoded as! CVarArg, contentId)
        let postData = post.data(using: String.Encoding.utf8, allowLossyConversion: true)
        request.httpBody = postData

        return request
    }

    static func fetchSpcData(
        loadingRequest: AVAssetResourceLoadingRequest,
        certificateData: Data,
        contentIdData: Data
    ) -> Promise<Data> {
        return Promise<Data>(on: .global()) { fulfill, reject in
            #if os(visionOS)
                // TODO: DRM is not supported yet on visionOS. See #3467
                reject(NSError(domain: "DRM is not supported yet on visionOS", code: 0, userInfo: nil))
            #else
                guard let spcData = try? loadingRequest.streamingContentKeyRequestData(
                    forApp: certificateData,
                    contentIdentifier: contentIdData as Data,
                    options: nil
                ) else {
                    reject(RCTVideoErrorHandler.noSPC)
                    return
                }

                fulfill(spcData)
            #endif
        }
    }

    static func createCertificateData(certificateStringUrl: String?, base64Certificate: Bool?) -> Promise<Data> {
        return Promise<Data>(on: .global()) { fulfill, reject in
            guard let certificateStringUrl,
                  let certificateURL = URL(string: certificateStringUrl.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed) ?? "") else {
                reject(RCTVideoErrorHandler.noCertificateURL)
                return
            }

            var certificateData: Data?
            do {
                certificateData = try Data(contentsOf: certificateURL)
                if base64Certificate != nil {
                    certificateData = Data(base64Encoded: certificateData! as Data, options: .ignoreUnknownCharacters)
                }
            } catch {}

            guard let certificateData else {
                reject(RCTVideoErrorHandler.noCertificateData)
                return
            }

            fulfill(certificateData)
        }
    }

    static func handleWithOnGetLicense(loadingRequest: AVAssetResourceLoadingRequest, contentId: String?, certificateUrl: String?,
                                       base64Certificate: Bool?) -> Promise<Data> {
        let contentIdData = contentId?.data(using: .utf8)

        return RCTVideoDRM.createCertificateData(certificateStringUrl: certificateUrl, base64Certificate: base64Certificate)
            .then { certificateData -> Promise<Data> in
                guard let contentIdData else {
                    throw RCTVideoError.invalidContentId as! Error
                }

                return RCTVideoDRM.fetchSpcData(
                    loadingRequest: loadingRequest,
                    certificateData: certificateData,
                    contentIdData: contentIdData
                )
            }
    }

    static func handleInternalGetLicense(
        loadingRequest: AVAssetResourceLoadingRequest,
        contentId: String?,
        licenseServer: String?,
        certificateUrl: String?,
        base64Certificate: Bool?,
        headers: [String: Any]?
    ) -> Promise<Data> {
        let url = loadingRequest.request.url

        guard let contentId = contentId ?? url?.absoluteString.replacingOccurrences(of: "skd://", with: "") else {
            return Promise(RCTVideoError.invalidContentId as! Error)
        }

        let contentIdData = NSData(bytes: contentId.cString(using: String.Encoding.utf8), length: contentId.lengthOfBytes(using: String.Encoding.utf8)) as Data

        return RCTVideoDRM.createCertificateData(certificateStringUrl: certificateUrl, base64Certificate: base64Certificate)
            .then { certificateData in
                return RCTVideoDRM.fetchSpcData(
                    loadingRequest: loadingRequest,
                    certificateData: certificateData,
                    contentIdData: contentIdData
                )
            }
            .then { spcData -> Promise<Data> in
                guard let licenseServer else {
                    throw RCTVideoError.noLicenseServerURL as! Error
                }
                return RCTVideoDRM.fetchLicense(
                    licenseServer: licenseServer,
                    spcData: spcData,
                    contentId: contentId,
                    headers: headers
                )
            }
    }
}
