package com.bytelyst.platform

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.util.UUID
import java.util.concurrent.TimeUnit

/**
 * Generic HTTP client for platform-service.
 *
 * Injects auth token, x-product-id, and x-request-id on every request.
 * Supports fire-and-forget mode for telemetry-style calls.
 */
class BLPlatformClient(
    private val config: BLPlatformConfig,
    private val tokenProvider: () -> String? = { null },
) {
    val json = Json { ignoreUnknownKeys = true; encodeDefaults = true }

    private val httpClient = OkHttpClient.Builder()
        .connectTimeout(config.timeoutMs, TimeUnit.MILLISECONDS)
        .writeTimeout(config.timeoutMs, TimeUnit.MILLISECONDS)
        .readTimeout(config.timeoutMs, TimeUnit.MILLISECONDS)
        .build()

    /**
     * Execute a request and return the response body as a String.
     * Throws on non-2xx responses.
     */
    suspend fun request(
        method: String,
        path: String,
        body: String? = null,
        extraHeaders: Map<String, String> = emptyMap(),
        skipAuth: Boolean = false,
    ): String = withContext(Dispatchers.IO) {
        val builder = Request.Builder()
            .url("${config.baseUrl}$path")
            .header("Content-Type", "application/json")
            .header("X-Product-Id", config.productId)
            .header("X-Request-Id", UUID.randomUUID().toString())

        if (!skipAuth) {
            tokenProvider()?.let { token ->
                builder.header("Authorization", "Bearer $token")
            }
        }

        extraHeaders.forEach { (k, v) -> builder.header(k, v) }

        val requestBody = body?.toRequestBody("application/json".toMediaType())
        when (method.uppercase()) {
            "GET" -> builder.get()
            "POST" -> builder.post(requestBody ?: "".toRequestBody(null))
            "PUT" -> builder.put(requestBody ?: "".toRequestBody(null))
            "DELETE" -> if (requestBody != null) builder.delete(requestBody) else builder.delete()
            "PATCH" -> builder.patch(requestBody ?: "".toRequestBody(null))
            else -> builder.method(method, requestBody)
        }

        val response = httpClient.newCall(builder.build()).execute()
        val responseBody = response.body?.string() ?: ""

        if (!response.isSuccessful) {
            throw BLApiException(response.code, responseBody)
        }

        responseBody
    }

    /**
     * Fire-and-forget: execute request, silently swallow errors.
     */
    suspend fun fireAndForget(
        method: String,
        path: String,
        body: String? = null,
        extraHeaders: Map<String, String> = emptyMap(),
    ) {
        try {
            request(method, path, body, extraHeaders)
        } catch (_: Exception) {
            // Silently swallow — fire-and-forget
        }
    }
}

/**
 * Exception thrown when the platform API returns a non-2xx status.
 */
class BLApiException(
    val statusCode: Int,
    val responseBody: String,
) : Exception("Platform API error $statusCode: $responseBody")
