//
//  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?

    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
        guard var components = URLComponents(string: urlString) else { return (nil, .missingUrl) }
        // Ensure path percent encoding is applied correctly
        components.percentEncodedPath = components.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? components.path
        guard let url = components.url else { return (nil, .missingUrl) }

        // 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
        do {
            let (data, response) = try await URLSession.shared.data(for: request)
            if let error = decodeURLResponse(response: response as? HTTPURLResponse, data: data) {
                Logger.network.debug("\(method.rawValue) \(path) FAILURE: \(error.localizedDescription)")
                return (nil, error)
            }
            Logger.network.debug("\(method.rawValue) \(path) SUCCESS!")
            return (data, nil)
        } catch {
            Logger.network.debug("\(method.rawValue) \(path) FAILURE: \(error.localizedDescription)")
            return (nil, .connectionError(underlying: error))
        }
    }

    private func decodeURLResponse(response: HTTPURLResponse?, data: Data?) -> NetworkError? {
        guard let response = response else {
            return .unknownError(detail: "No HTTP response")
        }
        let statusCode = response.statusCode
        switch statusCode {
        case 200...299:
            return nil
        default:
            let body = data.flatMap { String(data: $0, encoding: .utf8) }
            return .httpError(statusCode: statusCode, body: body)
        }
    }
}
