package {{packageName}}.infrastructure

import io.vertx.core.Vertx
import io.vertx.core.buffer.Buffer
import java.nio.charset.StandardCharsets
{{#jackson}}
import com.fasterxml.jackson.core.type.TypeReference
{{/jackson}}
{{#gson}}
import com.google.gson.reflect.TypeToken
{{/gson}}

{{#nonPublicApi}}internal {{/nonPublicApi}}open class ApiClient(val basePath: kotlin.String = defaultBasePath, val accessToken: String? = null, val apiKey: MutableMap<String, String> = mutableMapOf(), val apiKeyPrefix: MutableMap<String, String> = mutableMapOf(), var username: String? = null, var password: String? = null, val vertx: Vertx) {
    companion object {
        const val baseUrlKey = "{{packageName}}.baseUrl"

        @JvmStatic
        val defaultBasePath: String by lazy {
            System.getProperties().getProperty(baseUrlKey, "{{{basePath}}}")
        }
    }

    protected inline fun <reified T: Any?> handleResponse(response: io.vertx.ext.web.client.HttpResponse<Buffer>): ApiResponse<T?> {
        val code = response.statusCode()
        val headers = response.headers().associate { it.key to listOf(it.value) }
        val contentType = headers["Content-Type"]?.firstOrNull()?.substringBefore(";")?.lowercase(java.util.Locale.getDefault())

        return when (code) {
            in 100..199 -> Informational(
                response.statusMessage(),
                code,
                headers
            )
            in 200 .. 299 -> Success(
                responseBody<T>(response.body(), contentType),
                code,
                headers
            )
            in 300..399 -> Redirection(
                code,
                headers
            )
            in 400..499 -> ClientError(
                response.statusMessage(),
                response.bodyAsString(),
                code,
                headers
            )
            else -> ServerError(
                response.statusMessage(),
                response.bodyAsString(),
                code,
                headers
            )
        }
    }

    protected inline fun <reified T: Any?> responseBody(body: Buffer?, mediaType: String? = "application/json"): T? {
        body ?: return null

        val bodyContent = String(body.bytes, StandardCharsets.UTF_8)
        if (bodyContent.isEmpty()) {
            return null
        }

        return when {
            mediaType==null || (mediaType.startsWith("application/") && mediaType.endsWith("json")) ->
                {{#moshi}}Serializer.moshi.adapter(T::class.java).fromJson(bodyContent){{/moshi}}{{!
                }}{{#gson}}Serializer.gson.fromJson(bodyContent, (object: TypeToken<T>(){}).getType()){{/gson}}{{!
                }}{{#jackson}}Serializer.jacksonObjectMapper.readValue(bodyContent, object: TypeReference<T>() {}){{/jackson}}{{!
                }}{{#kotlinx_serialization}}Serializer.kotlinxSerializationJson.decodeFromString<T>(bodyContent){{/kotlinx_serialization}}
            else ->  throw UnsupportedOperationException("responseBody currently only supports JSON body.")
        }
    }

    protected fun encodeURIComponent(parameter: String): String {
        return try {
            java.net.URLEncoder.encode(parameter, java.nio.charset.StandardCharsets.UTF_8.name())
        } catch (e: java.io.UnsupportedEncodingException) {
            parameter
        }
    }

    protected inline fun <reified T: Any> parseDateToQueryString(value : T): String {
        {{#toJson}}
        /*
        .replace("\"", "") converts the json object string to an actual string for the query parameter.
        The moshi or gson adapter allows a more generic solution instead of trying to use a native
        formatter. It also easily allows to provide a simple way to define a custom date format pattern
        inside a gson/moshi adapter.
        */
        {{#moshi}}
        return Serializer.moshi.adapter(T::class.java).toJson(value).replace("\"", "")
        {{/moshi}}
        {{#gson}}
        return Serializer.gson.toJson(value, T::class.java).replace("\"", "")
        {{/gson}}
        {{#jackson}}
        return Serializer.jacksonObjectMapper.writeValueAsString(value).replace("\"", "")
        {{/jackson}}
        {{#kotlinx_serialization}}
        return Serializer.kotlinxSerializationJson.encodeToString(value).replace("\"", "")
        {{/kotlinx_serialization}}
        {{/toJson}}
        {{^toJson}}
        return value.toString()
        {{/toJson}}
    }

}
