package ru.fincode.tsudesk.core.network

import retrofit2.HttpException
import retrofit2.Response
import ru.fincode.tsudesk.core.network.model.NetworkError
import ru.fincode.tsudesk.core.network.model.NetworkMeta
import ru.fincode.tsudesk.core.network.model.NetworkResult
import ru.fincode.tsudesk.core.network.model.NetworkResult.Success
import java.io.EOFException
import java.io.IOException
import java.io.InterruptedIOException
import java.net.ConnectException
import java.net.NoRouteToHostException
import java.net.SocketTimeoutException
import java.net.UnknownHostException

suspend inline fun <T> apiCall(
    crossinline block: suspend () -> Response<T>
): NetworkResult<T> {
    val startedAt = System.currentTimeMillis()

    return try {
        val response = block()
        val finishedAt = System.currentTimeMillis()

        val raw = response.raw()
        val meta = NetworkMeta(
            startedAtMillis = startedAt,
            finishedAtMillis = finishedAt,
            sentAtMillis = raw.sentRequestAtMillis,
            receivedAtMillis = raw.receivedResponseAtMillis
        )

        if (response.isSuccessful) {
            val body = response.body()
            if (body != null) {
                Success(data = body, meta = meta)
            } else {
                NetworkResult.Error(
                    NetworkError.Unknown(
                        throwable = NullPointerException("Response body is null"),
                        meta = meta
                    )
                )
            }
        } else {
            NetworkResult.Error(NetworkError.Http(code = response.code(), meta = meta))
        }
    } catch (t: Throwable) {
        val meta = NetworkMeta(
            startedAtMillis = startedAt,
            finishedAtMillis = System.currentTimeMillis()
        )
        NetworkResult.Error(t.toNetworkError(meta))
    }
}

fun Throwable.toNetworkError(meta: NetworkMeta): NetworkError = when (this) {

    is UnknownHostException,
    is ConnectException,
    is NoRouteToHostException -> NetworkError.NoInternet(meta)

    is SocketTimeoutException,
    is InterruptedIOException -> NetworkError.Timeout(meta)

    is EOFException -> NetworkError.Temporary(meta)

    is HttpException -> NetworkError.Http(code(), meta)

    is IOException -> {
        val msg = message?.lowercase().orEmpty()
        if ("unexpected end of stream" in msg || "eof" in msg) NetworkError.Temporary(meta)
        else NetworkError.Temporary(meta) // любые IO — временные, не "NoInternet"
    }

    else -> NetworkError.Unknown(this, meta)
}
