//
//  File.swift
//  
//
//  Created by TB_COWORKER on 14/06/2023.
//

import Foundation
import TrustBuilderMfaLibSwift
import os.log

public class UserApiResponseParser {

    public static func parseResponse<I, R, E, T: Codable>(request: APIRequest<I>, clientApi: APIClient, _ type: T.Type, listing: Bool = false) async throws -> ApiResponse<R, E> {
        let result = await withCheckedContinuation { continuation in
            clientApi.makeRequest(request) { response in
                continuation.resume(returning: response.data)
            }
        }
        return try decodeResultApi(T.self, result: result, listing: listing)
    }

    private static func decodeResultApi<R, E, T: Codable>(_ type: T.Type, result: Data?, listing: Bool = false) throws -> ApiResponse<R, E> {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        if (result == nil) {
            throw ApplicationError.USERAPI_ERROR("No result has been found from User API")
        }
        let resultEncoded = result!.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
        let decode = String(data: Data(base64Encoded: resultEncoded)!, encoding: .utf8)!
        let response = try! decoder.decode(T.self, from: Data(decode.utf8))
        if listing {
            return try buildListingApiResponse(response: response)
        }
        return try buildApiResponse(response: response)
    }

    private static func buildApiResponse<R, E, T: Codable>(response: T) throws -> ApiResponse<R, E> {
        let isErrorValue = try getPropertyValue(prop: "isError", response: response, objectType: Bool.self)
        if (!isErrorValue) {
            let responseValue = try! getPropertyValue(prop: "response", response: response, objectType: R.self)
            return ApiResponse<R, E>.Ok(response: responseValue)
        } else {
            let errorValue = try! getPropertyValue(prop: "error", response: response, objectType: AnyObject.self)
            let errorCodeValue = try! getPropertyValue(prop: "errorCode", response: errorValue, objectType: E.self)
            let errorMessageValue = try! getPropertyValue(prop: "errorMessage", response: errorValue, objectType: String.self)
            return ApiResponse<R, E>.Fail(error: errorCodeValue, message: errorMessageValue)
        }
    }
    
    private static func buildListingApiResponse<R, E, T: Codable>(response: T) throws -> ApiResponse<R, E> {
        return ApiResponse<R, E>.Ok(response: response as! R)
    }

    private static func getPropertyValue<T>(prop: String, response: Any, objectType: T.Type) throws -> T {
        let mirror = Mirror(reflecting: response)
        guard let property = mirror.children.first(where: { $0.label == prop }) else {
            throw ApplicationError.USERAPI_ERROR("Error retrieving property from server response. Might be an internal server error")
        }
        if (property.value as! T? == nil) {
            throw ApplicationError.USERAPI_ERROR("Error retrieving property value from server. Might be an internal server error")
        }
        return property.value as! T
    }
}
