package pl.firmatpp.kierowca.data

import java.io.IOException
import java.net.UnknownHostException
import retrofit2.HttpException

enum class ApiErrorKind {
    Network,
    Auth,
    Forbidden,
    Validation,
    Conflict,
    RateLimited,
    Server,
    Unknown,
}

data class ApiError(
    val kind: ApiErrorKind,
    val message: String,
    val retryable: Boolean,
    val code: String? = null,
    val statusCode: Int? = null,
    val responseBody: String? = null,
)

object ApiErrorMapper {
    fun map(throwable: Throwable): ApiError =
        when (throwable) {
            is UnknownHostException -> ApiError(
                kind = ApiErrorKind.Network,
                message = "Brak internetu lub połączenia z serwerem. Sprawdź zasięg i spróbuj ponownie.",
                retryable = true,
            )
            is IOException -> ApiError(
                kind = ApiErrorKind.Network,
                message = "Nie udało się połączyć z serwerem. Operacja nie została potwierdzona.",
                retryable = true,
            )
            is HttpException -> mapHttpException(throwable)
            else -> ApiError(
                kind = ApiErrorKind.Unknown,
                message = throwable.message ?: "Wystąpił błąd. Operacja nie została potwierdzona.",
                retryable = false,
            )
        }

    fun shouldReportNonFatal(throwable: Throwable): Boolean =
        map(throwable).kind != ApiErrorKind.Network

    fun mapHttpStatus(statusCode: Int, body: String?): ApiError {
        val code = body?.let { """"code"\s*:\s*"([^"]+)"""".toRegex().find(it)?.groupValues?.getOrNull(1) }
        val message = body?.let { """"message"\s*:\s*"([^"]+)"""".toRegex().find(it)?.groupValues?.getOrNull(1) }
        val retryable = body?.let { """"retryable"\s*:\s*(true|false)""".toRegex().find(it)?.groupValues?.getOrNull(1)?.toBooleanStrictOrNull() }

        return mapProblem(
            statusCode = statusCode,
            code = code,
            message = message,
            retryable = retryable,
            responseBody = body,
        )
    }

    fun mapProblem(
        statusCode: Int,
        code: String?,
        message: String?,
        retryable: Boolean?,
        responseBody: String? = null,
    ): ApiError {
        val kind = when (statusCode) {
            401 -> ApiErrorKind.Auth
            403 -> ApiErrorKind.Forbidden
            400, 422 -> ApiErrorKind.Validation
            409 -> ApiErrorKind.Conflict
            429 -> ApiErrorKind.RateLimited
            in 500..599 -> ApiErrorKind.Server
            else -> ApiErrorKind.Unknown
        }
        val defaultRetryable = statusCode == 429 || statusCode in 500..599
        val resolvedRetryable = retryable ?: defaultRetryable
        val resolvedMessage = message?.takeIf { it.isNotBlank() } ?: when (kind) {
            ApiErrorKind.Auth -> "Sesja wygasła. Zaloguj się ponownie."
            ApiErrorKind.Forbidden -> "Brak uprawnień do tej operacji."
            ApiErrorKind.Validation -> "Serwer odrzucił operację. Nie została zapisana."
            ApiErrorKind.Conflict -> "Operacja jest w konflikcie z aktualnym stanem danych."
            ApiErrorKind.RateLimited -> "Za dużo prób. Aplikacja spróbuje ponownie później."
            ApiErrorKind.Server -> "Serwer nie potwierdził operacji. Aplikacja spróbuje ponownie."
            ApiErrorKind.Network -> "Nie udało się połączyć z serwerem. Operacja nie została potwierdzona."
            ApiErrorKind.Unknown -> "Wystąpił błąd. Operacja nie została potwierdzona."
        }

        return ApiError(
            kind = kind,
            message = resolvedMessage,
            retryable = resolvedRetryable,
            code = code,
            statusCode = statusCode,
            responseBody = responseBody,
        )
    }

    private fun mapHttpException(exception: HttpException): ApiError {
        val body = runCatching { exception.response()?.errorBody()?.string() }.getOrNull()
        return mapHttpStatus(exception.code(), body)
    }
}
