//
//  ApiRequest.swift
//  Nextcloud Cookbook iOS Client
//
//  Created by Vincent Meilinger on 16.11.23.
//

import Foundation
import OSLog

struct ApiRequest {
    let path: String
    let method: RequestMethod
    let authString: String?
    let headerFields: [HeaderField]
    let body: Data?
    
    /// The path to the Cookbook application on the nextcloud server.
        
    init(
        path: String,
        method: RequestMethod,
        authString: String? = nil,
        headerFields: [HeaderField] = [],
        body: Data? = nil
    ) {
        self.method = method
        self.path = path
        self.headerFields = headerFields
        self.authString = authString
        self.body = body
    }
    
    func send(pathCompletion: Bool = true) async -> (Data?, NetworkError?) {
        Logger.network.debug("\(method.rawValue) \(path) sending ...")
        
        // Prepare URL
        let urlString = pathCompletion ? UserSettings.shared.serverProtocol + UserSettings.shared.serverAddress + path : path
        print("Full path: \(urlString)")
        //Logger.network.debug("Full path: \(urlString)")
        guard let urlStringSanitized = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return (nil, .unknownError) }
        guard let url = URL(string: urlStringSanitized) else { return (nil, .unknownError) }
        
        // Create URL request
        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        
        // Set authentication string, if needed
        if let authString = authString {
            request.setValue(
                "Basic \(authString)",
                forHTTPHeaderField: "Authorization"
            )
        }
        
        // Set other header fields
        for headerField in headerFields {
            request.setValue(
                headerField.getValue(),
                forHTTPHeaderField: headerField.getField()
            )
        }
        
        // Set http body
        if let body = body {
            request.httpBody = body
        }
                
        // Wait for and return data and (decoded) response
        var data: Data? = nil
        var response: URLResponse? = nil
        do {
            (data, response) = try await URLSession.shared.data(for: request)
            Logger.network.debug("\(method.rawValue) \(path) SUCCESS!")
            if let error = decodeURLResponse(response: response as? HTTPURLResponse) {
                print("\(method.rawValue) \(path) FAILURE: \(error.localizedDescription)")
                return (nil, error)
            }
            if let data = data {
                print(data, String(data: data, encoding: .utf8) as Any)
                return (data, nil)
            }
            return (nil, .unknownError)
        } catch {
            let error = decodeURLResponse(response: response as? HTTPURLResponse)
            Logger.network.debug("\(method.rawValue) \(path) FAILURE: \(error.debugDescription)")
            return (nil, error)
        }
    }
    
    private func decodeURLResponse(response: HTTPURLResponse?) -> NetworkError? {
        guard let response = response else {
            return NetworkError.unknownError
        }
        print("Status code: ", response.statusCode)
        switch response.statusCode {
            case 200...299: return (nil)
            case 300...399: return (NetworkError.redirectionError)
            case 400...499: return (NetworkError.clientError)
            case 500...599: return (NetworkError.serverError)
            case 600: return (NetworkError.invalidRequest)
            default: return (NetworkError.unknownError)
        }
    }
}
