//
//  Requestor.swift
//  StravaKit
//
//  Created by Brennan Stehling on 8/22/16.
//  Copyright © 2016 SmallSharpTools LLC. All rights reserved.
//

import Foundation
import Alamofire

/**
 Default Requestor used by the Strava class as the real implementation for requests.
 */
internal class DefaultRequestor: Requestor {
    
    /** Base URL for the API endpoints. */
    open var baseUrl: String
    
    init() {
        baseUrl = StravaBaseURL
    }
    
    /**
     Request method used for all API endpoint calls.
     
     - Parameters:
     - method: HTTP method
     - authenticated: Indicates if request is authenticated
     - path: Path for REST endpoing
     - params: Parameters for request
     - completionHandler: Callback
     - Returns: Task
     */
    open func request(_ method: HTTPMethod, authenticated: Bool, path: String, params: ParamsDictionary?, completionHandler: ((_ response: Any?, _ error: NSError?) -> ())?) -> URLSessionTask? {
        guard let url = Strava.urlWithString(baseUrl + path, parameters: method == .GET ? params : nil)
            else {
                let error = Strava.error(.unsupportedRequest, reason: "Unsupported Request")
                completionHandler?(nil, error)
                return nil
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        if method == .POST || method == .PUT {
            if let params = params {
                request.httpBody = convertParametersForBody(params)
            }
        }
        
        
        
        
        return processRequest(request, authenticated: authenticated, completionHandler: completionHandler)
        
    }
    
    
    
    // MARK: - Internal Functions -
    
    internal func processRequest(_ request: URLRequest, authenticated: Bool, completionHandler: ((_ response: Any?, _ error: NSError?) -> ())?) -> URLSessionTask? {
        let sessionConfiguration = URLSessionConfiguration.default
        if authenticated {
            guard let accessToken = Strava.sharedInstance.accessToken
                else {
                    let error = Strava.error(.noAccessToken, reason: "No Access Token")
                    completionHandler?(nil, error)
                    return nil
            }
            sessionConfiguration.httpAdditionalHeaders = ["Authorization": "Bearer \(accessToken)"]
        }
        let session = URLSession(configuration: sessionConfiguration)
        let task = session.dataTask(with: request, completionHandler: { [weak self] data, response, error in
            guard let httpResponse = response as? HTTPURLResponse
                else {
                    fatalError("Response must be an instance of NSHTTPURLResponse")
            }
            
            if Strava.isDebugging {
                debugPrint("Status Code: \(httpResponse.statusCode)")
                if let MIMEType = httpResponse.mimeType {
                    debugPrint("Status Code: \(MIMEType)")
                }
                if let data = data {
                    if let string = String(data: data, encoding: String.Encoding.utf8) {
                        debugPrint("Response: \(string)")
                    }
                }
            }
            
            if httpResponse.statusCode != 200 {
                if Strava.isDebugging {
                    self?.printResponse(httpResponse, data: data)
                }
                if httpResponse.statusCode == 401 {
                    let error = Strava.error(.accessForbidden, reason: "Access Forbidden")
                    completionHandler?(nil, error)
                    return
                }
                else if httpResponse.statusCode == 404 {
                    let error = Strava.error(.recordNotFound, reason: "Record Not Found")
                    completionHandler?(response, error)
                    return
                }
                else if httpResponse.statusCode == 500 {
                    let error = Strava.error(.remoteError, reason: "Remote Error")
                    completionHandler?(response, error)
                    return
                }
            }
            
            var response: Any? = nil
            if let data = data {
                do {
                    response = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as Any?
                }
                catch {
                    let error = Strava.error(.undefinedError, reason: "Failure while parsing response.")
                    completionHandler?(nil, error)
                    return
                }
            }
            
            if httpResponse.statusCode == 403 {
                if let json = response as? JSONDictionary,
                    let message = json["message"] as? String {
                    if message == "Rate Limit Exceeded" {
                        var userInfo: [String : Any]? = nil
                        if let limit = httpResponse.allHeaderFields[RateLimitLimitHeaderKey] as? String,
                            let usage = httpResponse.allHeaderFields[RateLimitUsageHeaderKey] as? String {
                            userInfo = [
                                RateLimitLimitKey : limit,
                                RateLimitUsageKey : usage
                            ]
                        }
                        
                        let error = Strava.error(.rateLimitExceeded, reason: "Rate Limit Exceeded", userInfo: userInfo)
                        completionHandler?(nil, error)
                    }
                    else {
                        let error = Strava.error(.accessForbidden, reason: "Access Forbidden", userInfo: nil)
                        completionHandler?(nil, error)
                    }
                }
                else {
                    let error = Strava.error(.accessForbidden, reason: "Access Forbidden")
                    completionHandler?(nil, error)
                }
            }
            else if response != nil {
                completionHandler?(response, error as NSError?)
            }
            else {
                let error = Strava.error(.undefinedError, reason: "Unknown Error")
                completionHandler?(nil, error)
            }
        })
        task.resume()
        return task
    }
    
    internal func convertParametersForBody(_ params: ParamsDictionary) -> Data? {
        return try? JSONSerialization.data(withJSONObject: params, options: [])
    }
    
    internal func printResponse(_ response: HTTPURLResponse, data: Data?) {
        guard let data = data else {
            return
        }
        
        let string = String(data: data, encoding: String.Encoding.utf8)
        print("Response: \(String(describing: string))")
    }
    
}

extension Requestor {
    
    public func uploadFitFile(url: String, params: [String : String]?, file: URL, completionHandler: ((_ response: Any?, _ error: NSError?) -> ())?) {
        
        guard let accessToken = Strava.sharedInstance.accessToken
            else {
                let error = Strava.error(.noAccessToken, reason: "No Access Token")
                completionHandler?(nil, error)
                return
        }
        debugPrint(accessToken)
        let au = "Bearer \(accessToken)"
        let headers = ["Authorization": au]
        
        
        Alamofire.upload(multipartFormData: { (multipartFormData) in
            if let params = params {
                for (key, value) in params {
                    multipartFormData.append(value.data(using: .utf8)!, withName: key)
                }
            }
            multipartFormData.append(file, withName: "file", fileName: file.lastPathComponent, mimeType: "multipart/form-data")
        }, usingThreshold: SessionManager.multipartFormDataEncodingMemoryThreshold, to: baseUrl + url, method: .post, headers: headers) { (encodingResult) in
            switch encodingResult {
            case .success(let upload, _, _):
                upload.responseJSON(completionHandler: { (response) in
                    
                    if let value = response.result.value {
                        let dict = value as! Dictionary<String, Any>;
                        var error = response.result.error as NSError?
                        if let err = dict["error"] {
                            if (err as AnyObject).isKind(of: NSNull.self) {
                                // 成功
                                debugPrint("成功")
                                
                            } else {
                                // 失败
                                debugPrint("失败",err)
                                error = NSError.init(domain: "strava", code: 0, userInfo: [NSLocalizedDescriptionKey:err])
                                
                            }
                            
                        }
                        
                        if let completionHandler = completionHandler {
                            completionHandler(response.result,error)
                        }
                    } else {
                        if let completionHandler = completionHandler {
                            if let error = response.result.error {
                                completionHandler(response.result,error as NSError)
                            }
                            
                        }
                        
                    }
                    
                })
            case .failure(let error):
                print(error)
            }
            
        }
        
    }
    
    
}

extension Requestor {
    
    
    /**
     Request method used for all API endpoint calls.
     
     - Parameters:
     - method: HTTP method
     - authenticated: Indicates if request is authenticated
     - path: Path for REST endpoing
     - params: Parameters for request
     - completionHandler: Callback
     - Returns: Task
     */
    @discardableResult
    func request(_ method: HTTPMethod, fromFile: URL, path: String, params: ParamsDictionary?, completionHandler: ((_ response: Any?, _ error: NSError?) -> ())?) -> URLSessionTask? {
        guard let url = Strava.urlWithString(baseUrl + path, parameters: method == .GET ? params : nil)
            else {
                let error = Strava.error(.unsupportedRequest, reason: "Unsupported Request")
                completionHandler?(nil, error)
                return nil
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        
        //        let fromData = try! Data.init(contentsOf: fromFile)
        
        //        let strLength = "\(fromData.count)"
        //        request.setValue(strLength, forHTTPHeaderField:"Content-Length")
        //        request.addValue("multipart/form-data", forHTTPHeaderField: "Content-Type")
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        
        //        request.addValue("text/html", forHTTPHeaderField: "Accept")
        if method == .POST || method == .PUT {
            if let params = params {
                request.httpBody = convertParametersForBody(params)
                
            }
        }
        //        request.httpBody = fromData
        
        return processRequest(request, from: fromFile, completionHandler: completionHandler)
    }
    
    // MARK: - Internal Functions -
    
    internal func processRequest(_ request: URLRequest, from url: URL, completionHandler: ((_ response: Any?, _ error: NSError?) -> ())?) -> URLSessionTask? {
        let sessionConfiguration = URLSessionConfiguration.default
        guard let accessToken = Strava.sharedInstance.accessToken
            else {
                let error = Strava.error(.noAccessToken, reason: "No Access Token")
                completionHandler?(nil, error)
                return nil
        }
        sessionConfiguration.httpAdditionalHeaders = ["Authorization": "Bearer \(accessToken)"]
        let session = URLSession(configuration: sessionConfiguration)
        
        
        let task = session.uploadTask(with: request,fromFile: url, completionHandler: {data, response, error in
            guard let httpResponse = response as? HTTPURLResponse
                else {
                    fatalError("Response must be an instance of NSHTTPURLResponse")
            }
            
            if Strava.isDebugging {
                debugPrint("Status Code: \(httpResponse.statusCode)")
                if let MIMEType = httpResponse.mimeType {
                    debugPrint("Status Code: \(MIMEType)")
                }
                if let data = data {
                    if let string = String(data: data, encoding: String.Encoding.utf8) {
                        debugPrint("Response: \(string)")
                    }
                }
            }
            
            if httpResponse.statusCode != 200 {
                if Strava.isDebugging {
                    self.printResponse(httpResponse, data: data)
                }
                if httpResponse.statusCode == 401 {
                    let error = Strava.error(.accessForbidden, reason: "Access Forbidden")
                    completionHandler?(nil, error)
                    return
                }
                else if httpResponse.statusCode == 404 {
                    let error = Strava.error(.recordNotFound, reason: "Record Not Found")
                    completionHandler?(response, error)
                    return
                }
                else if httpResponse.statusCode == 500 {
                    let error = Strava.error(.remoteError, reason: "Remote Error")
                    completionHandler?(response, error)
                    return
                }
            }
            
            var response: Any? = nil
            if let data = data {
                do {
                    response = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as Any?
                }
                catch {
                    let error = Strava.error(.undefinedError, reason: "Failure while parsing response.")
                    completionHandler?(nil, error)
                    return
                }
            }
            
            if httpResponse.statusCode == 403 {
                if let json = response as? JSONDictionary,
                    let message = json["message"] as? String {
                    if message == "Rate Limit Exceeded" {
                        var userInfo: [String : Any]? = nil
                        if let limit = httpResponse.allHeaderFields[RateLimitLimitHeaderKey] as? String,
                            let usage = httpResponse.allHeaderFields[RateLimitUsageHeaderKey] as? String {
                            userInfo = [
                                RateLimitLimitKey : limit,
                                RateLimitUsageKey : usage
                            ]
                        }
                        
                        let error = Strava.error(.rateLimitExceeded, reason: "Rate Limit Exceeded", userInfo: userInfo)
                        completionHandler?(nil, error)
                    }
                    else {
                        let error = Strava.error(.accessForbidden, reason: "Access Forbidden", userInfo: nil)
                        completionHandler?(nil, error)
                    }
                }
                else {
                    let error = Strava.error(.accessForbidden, reason: "Access Forbidden")
                    completionHandler?(nil, error)
                }
            }
            else if response != nil {
                completionHandler?(response, error as NSError?)
            }
            else {
                let error = Strava.error(.undefinedError, reason: "Unknown Error")
                completionHandler?(nil, error)
            }
        })
        task.resume()
        return task
    }
    
    internal func convertParametersForBody(_ params: ParamsDictionary) -> Data? {
        return try? JSONSerialization.data(withJSONObject: params, options: [])
    }
    
    internal func printResponse(_ response: HTTPURLResponse, data: Data?) {
        guard let data = data else {
            return
        }
        
        let string = String(data: data, encoding: String.Encoding.utf8)
        print("Response: \(String(describing: string))")
    }
    
}


