package com.github.shadowsocks.bg

import android.os.Build
import com.github.shadowsocks.Core
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.http.FreshToken
import com.github.shadowsocks.http.HttpConfig
import com.github.shadowsocks.http.LineConfig
import com.google.gson.Gson
import com.google.gson.JsonStreamParser
import com.google.gson.reflect.TypeToken
import com.tencent.mmkv.MMKV
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import timber.log.Timber
import java.io.File
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager

object AuthManager {

    val executor = Executors.newScheduledThreadPool(1)
    var scheduleAtFixedRate: ScheduledFuture<*>? = null

    val okHttpClient: OkHttpClient
    val gson: Gson


    private const val KEY_TOKEN = "com.ccwangluo.KEY_TOKEN"

    init {
        okHttpClient = OkHttpClient().newBuilder()
            .readTimeout(60, TimeUnit.SECONDS)//设置读取超时时间
            .writeTimeout(60, TimeUnit.SECONDS)//设置写的超时时间
            .connectTimeout(60, TimeUnit.SECONDS)//设置连接超时时间
            //https支持
            .hostnameVerifier { hostname, session -> true }
            .sslSocketFactory(initSSLSocketFactory(), initTrustManager())
            .build()
        gson = Gson()
    }

    var gameId: String?
        get() = MMKV.defaultMMKV().decodeString("CONFIG_GAMEID", null)
        set(gameId) {
            MMKV.defaultMMKV().putString("CONFIG_GAMEID", gameId)
        }
    var area: String?
        get() = MMKV.defaultMMKV().decodeString("CONFIG_AREA", null)
        set(gameId) {
            MMKV.defaultMMKV().putString("CONFIG_AREA", gameId)
        }

    var authToken: String?
        get() = MMKV.defaultMMKV().decodeString(KEY_TOKEN, null)
        set(token) {
            MMKV.defaultMMKV().putString(KEY_TOKEN, token)
        }

    // 加速时长
    var ACC_TIME: Long
        get() = MMKV.defaultMMKV().decodeLong("ACC_TIME", 0L)
        set(acc_time) {
            MMKV.defaultMMKV().putLong("ACC_TIME", acc_time)
        }

    var Last_ACC_TIme:Long
        get() = MMKV.defaultMMKV().decodeLong("LAST_ACC_TIME", 0L)
        set(last_acc_time) {
            MMKV.defaultMMKV().putLong("LAST_ACC_TIME", last_acc_time)
        }

    suspend fun initAuthProfile(profile: Profile) {
        withContext(Dispatchers.IO) {
            getLineConfig()?.let {
                if (it.code != 200) {
                    return@withContext
                }
                profile.host = it.data.host
                profile.token = it.data.token
                profile.remotePort = it.data.port
                Timber.d("start vpn with games: ${profile.individual}, profileId: ${profile.id}, route: ${profile.route}")
                ProfileManager.updateProfile(profile)
            }
        }
    }

    fun updateConfig(data: BaseService.Data) {
        if (data.state != BaseService.State.Connected) {
            return
        }
        scheduleAtFixedRate?.cancel(false)
        scheduleAtFixedRate = executor.scheduleAtFixedRate({
            val context =
                if (Build.VERSION.SDK_INT < 24 || Core.user.isUserUnlocked) Core.app else Core.deviceStorage
            val configFile = File(context.noBackupFilesDir, BaseService.CONFIG_FILE)
            val configJson = JsonStreamParser(configFile.readText()).asSequence()
                .single().asJsonObject

            if (data.state == BaseService.State.Connected) {
                data.proxy?.let { proxyInstance ->
                    getToken(proxyInstance.profile.token ?: "")?.let {
                        if (it.code == 200) {
                            proxyInstance.profile.token = it.data.token
                            configJson.addProperty("token", it.data.token)
                            configFile.writeText(configJson.toString())
                        }
                    }
                }
            }

        }, 1, 1, TimeUnit.MINUTES)
    }

    fun getToken(token: String): FreshToken? {
        val map = mapOf(Pair("token", token))
        return postSync<FreshToken>("/api/acc/line/refresh", map)
    }


    val jsonType get() = "application/json; charset=utf-8".toMediaTypeOrNull()

    private fun getLineConfig(
    ): LineConfig? {
        val map = mutableMapOf<String, String>()
        area?.let { map.put("area", it) }
        gameId?.let {
            map.put("gameId", it)
        }
        return postSync<LineConfig>("/api/acc/line", map)

    }

    inline fun <reified T> postSync(
        url: String,
        map: Map<String, String>
    ): T? {
        try {
            val body: RequestBody = gson.toJson(map).toString().toRequestBody(jsonType)
            val request = Request.Builder()
                .url(HttpConfig.baseUrl + url)
                .post(body)

            authToken?.let {
                request.addHeader("Authorization", it)
            }
            val call = okHttpClient.newCall(request.build())
            val response: Response = call.execute()
            if (response.isSuccessful) {
                val string = response.body?.string()
                println(string)
                val type = object : TypeToken<T>() {}.type
                return gson.fromJson(string, type)
            }
            return null
        } catch (e: java.lang.Exception) {
            println(e)
        }
        return null
    }

    private fun initSSLSocketFactory(): SSLSocketFactory {
        var sslContext: SSLContext? = null
        try {
            sslContext = SSLContext.getInstance("SSL")
            val xTrustArray = arrayOf(initTrustManager())
            sslContext.init(
                null,
                xTrustArray, SecureRandom()
            )
        } catch (e: Exception) {
            e.printStackTrace()
        }


        return sslContext!!.socketFactory
    }

    private fun initTrustManager(): X509TrustManager {
        return object : X509TrustManager {
            override fun getAcceptedIssuers(): Array<X509Certificate> {
                return arrayOf()
            }

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

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

}