package com.example.vastlib.utils

import android.annotation.SuppressLint
import android.net.TrafficStats
import com.example.vastlib.entity.http.Request
import com.example.vastlib.entity.http.Response
import com.example.vastlib.utils.Gzip.compress
import com.example.vastlib.utils.Gzip.unCompress
import com.example.vastlib.utils.JSONUtils.toJsonString
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.IOException
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.Proxy
import java.net.URL
import java.security.cert.X509Certificate
import java.util.zip.GZIPInputStream
import javax.net.ssl.*

class HttpUtils {

    object Params {
        const val REQUEST_METHOD_POST = "POST"
        const val REQUEST_METHOD_GET = "GET"
        const val REQUEST_HEADER_CONTENT_TYPE = "Content-Type"
        const val REQUEST_HEADER_ACCEPT = "Accept"
        const val REQUEST_HEADER_USER_AGENT = "User-Agent"
        const val REQUEST_HEADER_ACCEPT_LANGUAGE = "Accept-Language"
        const val REQUEST_HEADER_COOKIE = "Cookie"
        const val REQUEST_HEADER_LOCATION = "Location"
        const val REQUEST_HEADER_REFERER = "Referer"
        const val RESPONSE_HEADER_SET_COOKIE = "Set-Cookie"
        const val REQUEST_HEADER_CONTENT_TYPE_STREAM = "application/octet-stream"
    }

    object ErrorCode {
        const val ERROR_CODE_HTTP_BUILD_CONNECTION_FAILED = 610
        const val ERROR_CODE_HTTP_NO_SUCH_METHOD = 611
        const val ERROR_CODE_HTTP_ESTABLISH_CONNECTION_FAILED = 612
        const val ERROR_CODE_HTTP_SEND_DATA_FAILED = 613
        const val ERROR_CODE_HTTP_RECEIVE_DATA_FAILED = 614
        const val ERROR_CODE_HTTP_CALL_REQUEST_FAILED = 615
        const val TIME_OUT = 60000
    }

    sealed class RequestMethod {
        object Post : RequestMethod()
        object Get : RequestMethod()
    }


    @SuppressLint("TrustAllX509TrustManager")
    object HttpRequest {

        private fun URL.createConnection(): HttpURLConnection {
            TrafficStats.setThreadStatsTag(Thread.currentThread().hashCode())
            val trustManager = @SuppressLint("CustomX509TrustManager") object : X509TrustManager {
                override fun checkClientTrusted(
                    chain: Array<out X509Certificate>?, authType: String?
                ) {
                }

                override fun checkServerTrusted(
                    chain: Array<out X509Certificate>?, authType: String?
                ) {
                }

                override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
            }

            val getEmptySSLFactory = run {
                SSLContext.getInstance("TLS").apply {
                    init(null, arrayOf(trustManager), null)
                }.socketFactory
            }

            return if (this.protocol.equals("https", ignoreCase = true)) {
                (this.openConnection(Proxy.NO_PROXY) as HttpsURLConnection).apply {
                    sslSocketFactory = getEmptySSLFactory
                    setHostnameVerifier { _, _ -> true }
                }
            } else {
                this.openConnection(Proxy.NO_PROXY) as HttpURLConnection
            }


        }

        fun HttpURLConnection.call(
            headers: Map<String, String> = emptyMap(),
            body: ByteArray = byteArrayOf(),
            encryptor: Encryptor? = null,
            method: RequestMethod = RequestMethod.Get
        ): Int {
            var responseCode = 0
            var body = body
            connectTimeout = ErrorCode.TIME_OUT
            readTimeout = ErrorCode.TIME_OUT
            if (body.isNotEmpty() && encryptor != null) {
                LogUtils.info("encrypt: ${encryptor.key}")
                body = body.compress().encrypt(encryptor.key)
                LogUtils.info("call url $url post encrypt data")
            }

            setRequestProperty("Host", url.host)
            setRequestProperty("Connection", "keep-alive")

            headers.map {
                addRequestProperty(it.key, it.value)
            }

            if (method == RequestMethod.Post && body.isNotEmpty()) {
                setRequestProperty("Content-Length", "${body.size}")
                doOutput = true
                outputStream.runCatching {
                    use {
                        write(body)
                        flush()
                    }
                }.onFailure { e ->
                    responseCode = ErrorCode.ERROR_CODE_HTTP_SEND_DATA_FAILED
                    LogUtils.error(throwable = e)
                }

            } else {
                doOutput = false
            }

            runCatching {
                connect()
            }.onFailure { e ->
                responseCode = ErrorCode.ERROR_CODE_HTTP_ESTABLISH_CONNECTION_FAILED
                LogUtils.error(throwable = e)
            }

            return responseCode
        }


        suspend fun Request.call(): Response {
            val request = this
            val response = Response()
            withContext(Dispatchers.IO) {
                LogUtils.info("call request: ${request.toJsonString()}")
                val startTimestamp = System.currentTimeMillis()
                var responseCode = 0
                var responseHeaders: MutableMap<String, MutableList<String>> = mutableMapOf()
                var responseData: ByteArray = byteArrayOf()
                var errorMsg = ""
                var urlConnection: HttpURLConnection? = null
                runCatching {
                    LogUtils.info("${System.currentTimeMillis()} start call http $method request $url")
                    runCatching {
                        urlConnection = URL(url).createConnection()
                    }.onFailure { e ->
                        errorMsg = e.message ?: ""
                        responseCode = ErrorCode.ERROR_CODE_HTTP_NO_SUCH_METHOD
                        LogUtils.error(throwable = e)
                        return@withContext response
                    }

                    runCatching {
                        urlConnection?.requestMethod = method
                    }.onFailure { e ->
                        errorMsg = e.message ?: ""
                        responseCode = ErrorCode.ERROR_CODE_HTTP_NO_SUCH_METHOD
                        LogUtils.error(throwable = e)
                        return@withContext response
                    }
                    urlConnection?.doInput = true
                    urlConnection?.instanceFollowRedirects = false
                    urlConnection?.useCaches = false

                    var callRequestCode = 0
                    val requestHeaders = headers
                    when (method.uppercase()) {
                        Params.REQUEST_METHOD_GET.uppercase() -> {
                            urlConnection?.runCatching {
                                callRequestCode = if (requestHeaders.isNotEmpty()) {
                                    call(headers = requestHeaders, method = RequestMethod.Get)
                                } else {
                                    call(method = RequestMethod.Get)
                                }
                            }
                        }
                        Params.REQUEST_METHOD_POST.uppercase() -> {
                            var encryptor: Encryptor? = null
                            if (request.isEncryptRequest()) {
                                urlConnection?.let {
                                    encryptor = Encryptor(it.url.host)
                                }
                            }
                            urlConnection?.runCatching {

                                callRequestCode = if (requestHeaders.isNotEmpty()) {
                                    call(
                                        requestHeaders,
                                        body,
                                        encryptor,
                                        method = RequestMethod.Post
                                    )
                                } else {
                                    call(
                                        body = body,
                                        encryptor = encryptor,
                                        method = RequestMethod.Post
                                    )
                                }
                            }?.onFailure {
                                LogUtils.error(it, "aaaaa")
                            }
                        }
                        else -> {}
                    }

                    if (callRequestCode != 0) {
                        responseCode = callRequestCode
                        return@withContext response
                    }

                    urlConnection?.runCatching {
                        responseCode = getResponseCode()
                        responseHeaders = headerFields
                        responseData = readData()
                        LogUtils.info("${System.currentTimeMillis()} end call http request $url")
                    }?.onFailure { e ->
                        errorMsg = e.message.toString()
                        responseCode = ErrorCode.ERROR_CODE_HTTP_RECEIVE_DATA_FAILED
                        LogUtils.error(throwable = e)
                        return@withContext response
                    }

                    if (isEncryptRequest()) {
                        urlConnection?.let {
                            val encryptor = Encryptor(it.url.host)
                            responseData = responseData.decrypt(encryptor.key).unCompress()
                            LogUtils.info("call url $url received encrypt data ${String(responseData)}")
                        }
                    }
                }.onFailure { e ->
                    errorMsg = "Call Http Request Failed ${e.message}"
                    responseCode = ErrorCode.ERROR_CODE_HTTP_CALL_REQUEST_FAILED
                    LogUtils.error(throwable = e)
                    return@withContext response
                }
                response.code = responseCode
                response.headers = responseHeaders
                response.startTime = startTimestamp
                response.endTime = System.currentTimeMillis()
                if (errorMsg.isNotBlank()) {
                    LogUtils.info("error msg: $errorMsg")
                    response.data = errorMsg.toByteArray()
                }
                if (responseData.isNotEmpty()) {
                    response.data = responseData
                }
                LogUtils.info("got response ${response.toJsonString()}")
            }
            return response
        }

        fun Request.isEncryptRequest(): Boolean {
            if (headers.isEmpty()) return false
            return headers.any {
                Params.REQUEST_HEADER_ACCEPT.equals(
                    it.key, ignoreCase = true
                ) && Params.REQUEST_HEADER_CONTENT_TYPE_STREAM.equals(
                    it.value, ignoreCase = true
                )

            }
        }

        @Throws(IOException::class)
        fun HttpURLConnection.readData(): ByteArray {
            val inputStream = if (responseCode < 400) {
                inputStream
            } else {
                errorStream
            }

            val encoding = contentEncoding
            val isGzip = encoding?.lowercase()?.contains("gzip") ?: false
            return if (isGzip) {
                GZIPInputStream(inputStream).use {
                    it.readBytes()
                }
            } else {
                inputStream.readBytes()
            }
        }

    }


}