package com.github.shadowsocks.bg

import android.net.Network
import com.ccnet.acc.AccVpn
import com.ccnet.acc.NetworkAdapter
import com.github.shadowsocks.Core
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.http.*
import com.github.shadowsocks.preference.DataStore
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.tencent.mmkv.MMKV
import kotlinx.coroutines.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import timber.log.Timber
import java.io.IOException
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Socket
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
    var scheduleAtFixedRate1: ScheduledFuture<*>? = null

    private val okHttpClient: OkHttpClient
    val gson: Gson
    val mmkv = MMKV.mmkvWithID("InterProcessKV", MMKV.MULTI_PROCESS_MODE)

    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.decodeString("CONFIG_GAMEID", null)
        set(gameId) {
            mmkv.putString("CONFIG_GAMEID", gameId)
        }
    var area: String?
        get() = mmkv.decodeString("CONFIG_AREA", null)
        set(gameId) {
            mmkv.putString("CONFIG_AREA", gameId)
        }

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

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

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

    /**
     * 智能加速是否启动
     */
    var isSmartAccOpen: Boolean
        get() = mmkv.decodeBool("IS_SMART_ACC_OPEN", true)
        set(isSmartAccOpen) {
            mmkv.putBoolean("IS_SMART_ACC_OPEN", isSmartAccOpen)
        }

    class AuthException(msg: String) : IOException(msg), BaseService.ExpectedException


    suspend fun initAuthProfile(profile: Profile) {
        withContext(Dispatchers.IO) {
            val gameId: Int = gameId?.toInt() ?: throw AuthException("游戏不存在")
            val lineConfigResult =
                    postSync<LineConfig>("/api/acc/line", authToken, LineConfigReq(gameId, area))
            if (lineConfigResult.isSuccessful()) {
                val lineConfig = lineConfigResult.unwrapData()
                profile.host = lineConfig.host
                profile.token = lineConfig.token
                profile.remotePort = lineConfig.port

                DataStore.pingProxyServer = profile.host
                DataStore.pingProxyPort = profile.remotePort + 1
                Timber.d("start vpn with games: ${profile.individual}, profileId: ${profile.id}, route: ${profile.route}")
                ProfileManager.updateProfile(profile)
            } else if (lineConfigResult.code == 1001) {
                throw AuthException(lineConfigResult.error!!)
            } else {
                throw AuthException(lineConfigResult.error!!)
            }
        }
    }

    fun updateConfig(data: BaseService.Data) {
        if (data.state != BaseService.State.Connected) {
            return
        }
        NetworkAdapter.initNetworks(Core.app)
        scheduleAtFixedRate?.cancel(false)
        var failedCount: Int = 0
        scheduleAtFixedRate = executor.scheduleAtFixedRate({
            if (data.state == BaseService.State.Connected) {
                data.proxy?.let { proxyInstance ->
                    val refreshTokenResult = postSync<RefreshTokenRes>(
                            "/api/acc/line/refresh", authToken,
                            RefreshTokenReq(proxyInstance.profile.token ?: "")
                    )
                    Timber.d("refreshTokenResult: $refreshTokenResult")
                    if (refreshTokenResult.isSuccessful()) {
                        failedCount = 0
                        proxyInstance.profile.token = refreshTokenResult.unwrapData().token
                        data.tunFd?.let {
                            AccVpn.writeConfig(Core.app, it, proxyInstance.profile)
                        }
                        return@let
                    } else if (refreshTokenResult.code == 1001) {
                        failedCount = 0
                        throw AuthException(refreshTokenResult.error!!)
                    } else {
                        failedCount++;
                        //连续失败超过3次抛异常
                        if (failedCount >= 3) {
                            failedCount = 0
                            throw AuthException("网络异常")
                        }
                    }
                }
            }

        }, 1, 1, TimeUnit.MINUTES)
        scheduleAtFixedRate1?.cancel(false)
        scheduleAtFixedRate1 = executor.scheduleAtFixedRate(
                {
                    Timber.d("testLatency")
                    GlobalScope.launch {
                        testLatency()
                        if (wifiLatencyList.size == 5) {
                            val wifiavg = wifiLatencyList.average().toInt()
                            val celavg = cellularLatencyList.average().toInt()
                            if (wifiavg > celavg && wifiavg > 200 && celavg != -1) {
                                NetworkAdapter.useCellular(Core.deviceStorage)
                                Timber.d("switch to cellular")
                            } else if (celavg > wifiavg && wifiavg != -1) {
                                NetworkAdapter.useWifi(Core.deviceStorage)
                                Timber.d("switch to wifi")
                            }
                            wifiLatencyList.clear()
                            cellularLatencyList.clear()
                        }
                    }
                },
                1,
                1,
                TimeUnit.SECONDS
        )
    }

    private val wifiLatencyList = mutableListOf<Int>()
    private val cellularLatencyList = mutableListOf<Int>()

    private suspend fun testLatency() {
        val networkAutoSwitch =
                isSmartAccOpen // 根据用户设置测试延迟 ，过高时切换 connection.bindNetwork(NetworkMode.CELLULAR.code)
        if (networkAutoSwitch) {
            val wifiLatency: Int = doTestLatency(
                    "114.114.114.114",
                    53,
                    NetworkAdapter.wifiNetwork
            )
            wifiLatencyList.add(wifiLatency)
            val cellularLatency: Int = doTestLatency(
                    "114.114.114.114",
                    53,
                    NetworkAdapter.cellularNetwork
            )
            cellularLatencyList.add(cellularLatency)
            Timber.d("wifi latency: $wifiLatency, cellular latency: $cellularLatency")
        }
    }

    private suspend fun doTestLatency(
            pingHost: String,
            pingPort: Int,
            network: Network?,
            systemNetwork: Boolean = false
    ): Int {
        return withTimeoutOrNull(DataStore.pingTimeoutMillis.toLong()) {
            try {
                val socketAddress: Socket = when {
                    network != null -> {
                        network.socketFactory.createSocket()
                    }
                    systemNetwork -> {
                        Socket()
                    }
                    else -> {
                        return@withTimeoutOrNull -1
                    }
                }
                val start = System.currentTimeMillis()
                socketAddress.connect(
                        InetSocketAddress(pingHost, pingPort),
                        DataStore.pingTimeoutMillis
                )
                (System.currentTimeMillis() - start).toInt()
            } catch (_: Exception) {
                -1
            }
        } ?: -1
    }

    var loginAuthToken: String = "1"

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

    private inline fun <reified T> postSync(
            url: String,
            token: String?,
            params: Any
    ): ApiResult<T> {
        val body: RequestBody = gson.toJson(params).toString().toRequestBody(jsonType)
        val request = Request.Builder()
                .url(if (url.startsWith("http")) url else HttpConfig.baseUrl + url)
                .post(body)
        token?.let {
            request.addHeader("Authorization", it)
        }
        val call = okHttpClient.newCall(request.build())
        val resultType = object : TypeToken<ApiResult<T>>() {}.type
        try {
            call.execute().use { response ->
                val string = response.body?.string()
                println(string)
                return gson.fromJson(string, resultType)
            }
        } catch (e: IOException) {
            Timber.e(e, "AuthManager 调用接口错误")
            return ApiResult(ApiError.NetworkError)
        }
    }

    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 AutoSwitchNetworkSocketFactory(sslContext!!.socketFactory)
    }

    private class AutoSwitchNetworkSocketFactory(private val delegate: SSLSocketFactory) : SSLSocketFactory() {
        override fun createSocket(s: Socket?, host: String?, port: Int, autoClose: Boolean): Socket {
            val bindNetwork = NetworkAdapter.bindNetwork
            val createSocket = delegate.createSocket(s, host, port, autoClose)
            bindNetwork?.bindSocket(createSocket)
            return createSocket
        }

        override fun createSocket(host: String?, port: Int): Socket {
            val bindNetwork = NetworkAdapter.bindNetwork
            return if (bindNetwork != null) {
                bindNetwork.socketFactory.createSocket(host, port)
            } else {
                delegate.createSocket(host, port)
            }
        }

        override fun createSocket(host: String?, port: Int, localHost: InetAddress?, localPort: Int): Socket {
            val bindNetwork = NetworkAdapter.bindNetwork
            return if (bindNetwork != null) {
                bindNetwork.socketFactory.createSocket(host, port, localHost, localPort)
            } else {
                delegate.createSocket(host, port, localHost, localPort)
            }
        }

        override fun createSocket(host: InetAddress?, port: Int): Socket {
            val bindNetwork = NetworkAdapter.bindNetwork
            return if (bindNetwork != null) {
                bindNetwork.socketFactory.createSocket(host, port)
            } else {
                delegate.createSocket(host, port)
            }
        }

        override fun createSocket(address: InetAddress?, port: Int, localAddress: InetAddress?, localPort: Int): Socket {
            val bindNetwork = NetworkAdapter.bindNetwork
            return if (bindNetwork != null) {
                bindNetwork.socketFactory.createSocket(address, port, localAddress, localPort)
            } else {
                delegate.createSocket(address, port, localAddress, localPort)
            }
        }

        override fun getDefaultCipherSuites(): Array<String> {
            return delegate.defaultCipherSuites
        }

        override fun getSupportedCipherSuites(): Array<String> {
            return delegate.supportedCipherSuites
        }

    }

    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?) {
            }
        }
    }

}