package com.example.modifier.http

import androidx.core.content.ContextCompat
import com.example.modifier.http.response.ErrorResponse
import com.example.modifier.repo.AppPrefsRepo
import com.example.modifier.utils.getContext
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.network.sockets.ConnectTimeoutException
import io.ktor.client.network.sockets.SocketTimeoutException
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.HttpRequestRetry
import io.ktor.client.plugins.HttpRequestTimeoutException
import io.ktor.client.plugins.HttpResponseValidator
import io.ktor.client.plugins.HttpSend
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.plugins.resources.Resources
import io.ktor.client.request.prepareGet
import io.ktor.client.utils.unwrapCancellationException
import io.ktor.http.contentLength
import io.ktor.serialization.kotlinx.json.json
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.isEmpty
import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import org.apache.commons.io.FileUtils
import org.apache.commons.lang3.RandomStringUtils
import java.io.File

fun Throwable.isTimeoutException(): Boolean {
    val exception = unwrapCancellationException()
    return exception is HttpRequestTimeoutException ||
            exception is ConnectTimeoutException ||
            exception is SocketTimeoutException
}

@OptIn(ExperimentalSerializationApi::class)
var ktorClient = HttpClient(OkHttp) {
    defaultRequest {
        val server = AppPrefsRepo.instance.appPrefs.value.server
        url(server.endsWith("/").let {
            if (it) server else "$server/"
        })
    }
    install(HttpSend) {
        maxSendCount = 3600
    }
    install(Logging) {
        level = LogLevel.NONE
    }
    install(HttpRequestRetry) {
        maxRetries = 3
        retryOnExceptionIf { _, cause ->
            when {
                cause.isTimeoutException() -> true
                cause is CancellationException -> false
                else -> true
            }
        }
        retryIf { _, response ->
            response.status.value.let {
                when (it) {
                    425, 429, 502, 503, 504 -> true
                    else -> false
                }
            }
        }
        delayMillis { 1000 }
    }
    install(HttpTimeout) {
        requestTimeoutMillis = 30 * 1000
        socketTimeoutMillis = 30 * 1000
    }
    install(Resources)
    install(ContentNegotiation) {
        json(Json {
            prettyPrint = true
            isLenient = true
            ignoreUnknownKeys = true
            explicitNulls = false
        })
    }
    HttpResponseValidator {
        validateResponse { response ->
            if (response.status.value !in 200..299) {
                val error = response.body<ErrorResponse>()
                throw ClientRequestException(response, error.message ?: "Unknown error")
            }
        }
    }
}

val fileCaching = mutableMapOf<String, String>()
suspend fun downloadImage(url: String): File {
    fileCaching[url]?.let {
        val file = File(it)
        if (file.exists()) {
            return file
        }
    }
    val imageDir = ContextCompat.getExternalFilesDirs(getContext(), "images")[0]
    val file =
        File(imageDir, RandomStringUtils.randomAlphanumeric(32) + "." + url.substringAfterLast("."))
    ktorClient.prepareGet(url)
        .execute { httpResponse ->
            val channel: ByteReadChannel = httpResponse.body()
            while (!channel.isClosedForRead) {
                val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong())
                while (!packet.isEmpty) {
                    val bytes = packet.readBytes()
                    file.appendBytes(bytes)
                }
            }
        }
    fileCaching[url] = file.absolutePath
    return file
}