//
//  RESTHelper.swift
//  Arcade
//
//  Created by Aaron Wright on 4/23/19.
//  Copyright © 2019 A.C. Wright Design. All rights reserved.
//

import Foundation

public struct RESTHelper {
    
    public enum Method: String {
        case get = "GET"
        case post = "POST"
        case put = "PUT"
        case patch = "PATCH"
        case delete = "DELETE"
    }
    
    public struct Count: Decodable {
        var count: Int
    }
    
    private var jsonEncoder: JSONEncoder
    private var jsonDecoder: JSONDecoder
    
    init(jsonEncoder: JSONEncoder, jsonDecoder: JSONDecoder) {
        self.jsonEncoder = jsonEncoder
        self.jsonDecoder = jsonDecoder
    }
    
    public func url(configuration: RESTConfiguration, forTable table: Table, id: String? = nil, urlComponents: URLComponents = URLComponents()) -> URL? {
        var urlComponents = urlComponents
        urlComponents.scheme = configuration.apiScheme
        
        urlComponents.host = configuration.apiHost
        urlComponents.path = configuration.apiPath != nil ? "/\(configuration.apiPath!)/\(table.name)" : "/\(table.name)"
        urlComponents.port = configuration.apiPort
        
        if let id = id { urlComponents.path += "/\(id)" }
        
        configuration.queryOptions.forEach {
            if let value = $0.value as? String {
                if urlComponents.queryItems == nil {
                    urlComponents.queryItems = [URLQueryItem(name: $0.key, value: value)]
                } else {
                    urlComponents.queryItems?.append(URLQueryItem(name: $0.key, value: value))
                }
            }
        }
        
        return urlComponents.url
    }
    
    public func countUrl(configuration: RESTConfiguration, forTable table: Table, urlComponents: URLComponents = URLComponents()) -> URL? {
        var urlComponents = urlComponents
        urlComponents.scheme = configuration.apiScheme
        
        urlComponents.host = configuration.apiHost
        urlComponents.path = configuration.apiPath != nil ? "/\(configuration.apiPath!)/\(table.name)/count" : "/\(table.name)/count"
        urlComponents.port = configuration.apiPort
        
        configuration.queryOptions.forEach {
            if let value = $0.value as? String {
                if urlComponents.queryItems == nil {
                    urlComponents.queryItems = [URLQueryItem(name: $0.key, value: value)]
                } else {
                    urlComponents.queryItems?.append(URLQueryItem(name: $0.key, value: value))
                }
            }
        }
        
        return urlComponents.url
    }
    
    public func urlComponents(forQuery query: Query? = nil, search: String? = nil, sorts: [Sort] = [], limit: Int = 0, offset: Int = 0) throws -> URLComponents {
        var urlComponents = URLComponents()
        
        urlComponents.queryItems = try RESTQueryBuilder(query: query, search: search, sorts: sorts, limit: limit, offset: offset).queryItems()
        
        return urlComponents
    }
    
    public func urlRequest(forURL url: URL, method: Method, configuration: RESTConfiguration? = nil, data: Data? = nil) -> URLRequest {
        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        
        var headers = request.allHTTPHeaderFields ?? [:]
        headers["Content-Type"] = "application/json"
        headers["Accept"] = "application/json"
        
        if let authorization = configuration?.authorization {
            switch authorization {
            case .bearer(let token):
                headers["Authorization"] = "Bearer \(token)"
            case .basic(let username, let password):
                if let auth = "\(username):\(password)".data(using: .utf8)?.base64EncodedString() { headers["Authorization"] = "Basic \(auth)" }
            }
        }
        
        request.allHTTPHeaderFields = headers
        request.httpBody = data
        
        return request
    }
    
    public func encode<T>(value: T) throws -> Data? where T : Encodable {
        return try jsonEncoder.encode(value)
    }
    
    public func decode<T>(data: Data) throws -> T where T : Decodable {
        return try jsonDecoder.decode(T.self, from: data)
    }
    
    public func decodeViewable<T>(from data: Data, table: Table) throws -> T where T : Viewable {
        return try self.decode(data: data)
    }
    
    public func decodeArray<T>(from data: Data, table: Table) throws -> [T] where T : Viewable {
        return try self.decode(data: data)
    }
    
    public func decodeCount(from data: Data) throws -> Int {
        let count: Count = try self.decode(data: data)
        
        return count.count
    }
    
}
