package com.stove.unity

import androidx.annotation.Keep
import com.stove.auth.*
import com.stove.auth.operation.Operation
import com.stove.auth.termsofservice.TermsOfService
import com.stove.auth.termsofservice.TermsOfServiceData
import com.stove.base.json.putIgnoreException
import com.stove.base.log.Logger
import com.stove.base.result.Result
import com.unity3d.player.UnityPlayer
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import com.stove.base.log.model.LogLevel
import com.stove.base.log.model.LogTag
import com.stove.base.log.model.logTagList

@Keep
object AuthWrapper {
    private const val TAG = "AuthWrapper"
    private const val objectName = "StoveSDK"
    private const val functionName = "CallMessage"
    private const val IdentifierKey = "identifier"
    private const val ValueKey = "value"
    private const val ResultKey = "result"
    private const val PlayerIdKey = "playerId"
    private const val TermsOfServiceDataListKey = "termsOfServiceDataList"
    private const val AccessTokenKey = "accessToken"
    private const val notifyFunctionName = "NotifyMessage"

    @JvmStatic
    fun initialize(identifier: String) {
        val activity = UnityPlayer.currentActivity
        Auth.initialize(activity) { result ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    @JvmStatic
    fun initialize(version: String, identifier: String) {
        val activity = UnityPlayer.currentActivity
        Auth.initialize(activity, version) { result ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    private fun getProvider(providerClassName: String, jsonString: String? = null): Provider? {
        try {
            if (jsonString == null) {
                return Class.forName(providerClassName).newInstance() as Provider
            } else {
                val method = Class.forName(providerClassName).getMethod("from", String::class.java)
                val instance = method.invoke(null, jsonString) as Provider?

                if (instance == null) {
                    return Class.forName(providerClassName).newInstance() as Provider
                } else {
                    return instance
                }
            }
        } catch (e: Exception) {
            Logger.trace(
                LogLevel.ERROR,
                "$TAG.getProvider",
                exception = e
            )
        }
        return null
    }



    @JvmStatic
    fun login(providerClassName: String, identifier: String) {
        val activity = UnityPlayer.currentActivity
        val provider = getProvider(providerClassName)
        if (provider == null) {
            Logger.trace(
                LogLevel.ERROR,
                "$TAG.login",
                logTagList("provider is null")
            )
            return
        }
        Auth.login(activity, provider) { result: Result, accessToken: AccessToken? ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            valueJSONObject.putIgnoreException(AccessTokenKey, accessToken?.toJSONObject())
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    /**
     * @since 2.2.0
     */
    @JvmStatic
    fun loginGeneric(providerClassName: String, jsonString: String, identifier: String) {
        val activity = UnityPlayer.currentActivity
        val provider = getProvider(providerClassName)
        if (provider == null) {
            Logger.trace(
                LogLevel.ERROR,
                "$TAG.loginGeneric",
                logTagList("provider is null")
            )
            return
        }
        provider.map = convert(jsonString)
        Auth.login(activity, provider) { result: Result, accessToken: AccessToken? ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            valueJSONObject.putIgnoreException(AccessTokenKey, accessToken?.toJSONObject())
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    /**
     * @since 2.5.0
     */
    @JvmStatic
    fun loginGeneral(providerClassName: String, jsonString: String, identifier: String) {
        val activity = UnityPlayer.currentActivity
        val provider = getProvider(providerClassName, jsonString)
        if (provider == null) {
            Logger.trace(
                LogLevel.ERROR,
                "$TAG.loginGeneral",
                logTagList("provider is null")
            )
            return
        }
        Auth.login(activity, provider) { result: Result, accessToken: AccessToken? ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            valueJSONObject.putIgnoreException(AccessTokenKey, accessToken?.toJSONObject())
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    @JvmStatic
    fun register(providerClassName: String, termsOfServiceDataListString: String, identifier: String) {
        val activity = UnityPlayer.currentActivity
        val provider = getProvider(providerClassName)
        if (provider == null) {
            Logger.trace(
                LogLevel.ERROR,
                "$TAG.register",
                logTagList("provider is null")
            )
            return
        }
        val list = makeTermsOfServiceDataList(termsOfServiceDataListString)
        Auth.register(activity, provider, list) { result: Result, accessToken: AccessToken? ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            valueJSONObject.putIgnoreException(AccessTokenKey, accessToken?.toJSONObject())
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    /**
     * @since 2.2.0
     */
    @JvmStatic
    fun registerGeneric(providerClassName: String, jsonString: String, termsOfServiceDataListString: String, identifier: String) {
        val activity = UnityPlayer.currentActivity
        val provider = getProvider(providerClassName)
        if (provider == null) {
            Logger.trace(
                LogLevel.ERROR,
                "$TAG.registerGeneric",
                logTagList("provider is null")
            )
            return
        }
        provider.map = convert(jsonString)
        val list = makeTermsOfServiceDataList(termsOfServiceDataListString)
        Auth.register(activity, provider, list) { result: Result, accessToken: AccessToken? ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            valueJSONObject.putIgnoreException(AccessTokenKey, accessToken?.toJSONObject())
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    @JvmStatic
    fun link(providerClassName: String, identifier: String) {
        val activity = UnityPlayer.currentActivity
        val provider = getProvider(providerClassName)
        if (provider == null) {
            Logger.trace(
                LogLevel.ERROR,
                "$TAG.link",
                logTagList("provider is null")
            )
            return
        }
        Auth.accessToken?.user?.link(activity, provider) { result: Result ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    /**
     * @since 2.2.0
     */
    @JvmStatic
    fun linkGeneric(providerClassName: String, jsonString: String, identifier: String) {
        val activity = UnityPlayer.currentActivity
        val provider = getProvider(providerClassName)
        if (provider == null) {
            Logger.trace(
                LogLevel.ERROR,
                "$TAG.linkGeneric",
                logTagList("provider is null")
            )
            return
        }
        provider.map = convert(jsonString)
        Auth.accessToken?.user?.link(activity, provider) { result: Result ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    /**
     * @since 2.5.0
     */
    @JvmStatic
    fun linkGeneral(providerClassName: String, jsonString: String, identifier: String) {
        val activity = UnityPlayer.currentActivity
        val provider = getProvider(providerClassName, jsonString)
        if (provider == null) {
            Logger.trace(
                LogLevel.ERROR,
                "$TAG.linkGeneral",
                logTagList("provider is null")
            )
            return
        }
        Auth.accessToken?.user?.link(activity, provider) { result: Result ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    @JvmStatic
    fun fetchOperation(identifier: String) {
        val context = UnityPlayer.currentActivity.applicationContext
        Operation.fetch(context) { result: Result ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    @JvmStatic
    fun logout() {
        val context = UnityPlayer.currentActivity.applicationContext
        Auth.logout(context)
    }

    @JvmStatic
    fun getAccessToken(): String? {
        return Auth.accessToken?.toJSONObject()?.toString()
    }

    /**
     * @since 2.2.0
     */
    @JvmStatic
    fun setAccessToken(accessTokenString: String) {
        val context = UnityPlayer.currentActivity.applicationContext
        try {
            val accessToken = AccessToken(JSONObject(accessTokenString))
            Auth.setAccessToken(context, accessToken)
        } catch (e: JSONException) {
            Logger.trace(
                LogLevel.ERROR,
                "$TAG.setAccessToken",
                exception = e
            )
            return
        }
    }

    @JvmStatic
    fun refresh(identifier: String) {
        val context = UnityPlayer.currentActivity.applicationContext
        Auth.accessToken?.refresh(context) { result ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    @JvmStatic
    fun fetch(identifier: String) {
        val context = UnityPlayer.currentActivity.applicationContext
        TermsOfService.fetch(context) { result, list ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            valueJSONObject.putIgnoreException(TermsOfServiceDataListKey, makeTermsOfServiceData(list))
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    @JvmStatic
    fun fetchForRegister(identifier: String) {
        val context = UnityPlayer.currentActivity.applicationContext
        TermsOfService.fetchForRegister(context) { result, list ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            valueJSONObject.putIgnoreException(TermsOfServiceDataListKey, makeTermsOfServiceData(list))
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    @JvmStatic
    fun save(termsOfServiceDataListString: String, identifier: String) {
        val context = UnityPlayer.currentActivity.applicationContext
        val list = makeTermsOfServiceDataList(termsOfServiceDataListString)
        TermsOfService.save(context, list) { result ->
            val jsonObject = JSONObject()
            jsonObject.putIgnoreException(IdentifierKey, identifier)
            val valueJSONObject = JSONObject()
            valueJSONObject.putIgnoreException(ResultKey, result.toJSONObject())
            jsonObject.putIgnoreException(ValueKey, valueJSONObject)
            val params = jsonObject.toString()
            UnityPlayer.UnitySendMessage(objectName, functionName, params)
        }
    }

    @JvmStatic
    fun setGameProfile(gameProfileString: String) {
        val context = UnityPlayer.currentActivity.applicationContext
        if (gameProfileString.isEmpty()) {
            Auth.accessToken?.user?.setGameProfile(context, null)
        } else {
            val gameProfile = GameProfile.from(gameProfileString)
            if (gameProfile == null) {
                Logger.trace(
                    LogLevel.ERROR,
                    "$TAG.setGameProfile",
                    logTagList("gameProfile is null")
                )
                return
            }
            Auth.accessToken?.user?.setGameProfile(context, gameProfile)
        }
    }

    /**
     * @since 2.1.0
     */
    @JvmStatic
    fun setGameProfileProperties(propertiesString: String) {
        val context = UnityPlayer.currentActivity.applicationContext
        if (propertiesString.isEmpty()) {
            Auth.accessToken?.user?.gameProfile?.setProperties(context, JSONObject())
        } else {
            val properties = try {
                JSONObject(propertiesString)
            } catch (e: JSONException) {
                Logger.trace(
                    LogLevel.ERROR,
                    "$TAG.setGameProfileProperties",
                    exception = e
                )
                return
            }
            Auth.accessToken?.user?.gameProfile?.setProperties(context, properties)
        }
    }

    private fun makeTermsOfServiceDataList(termsOfServiceDataListString: String): List<TermsOfServiceData> {
        val list = mutableListOf<TermsOfServiceData>()
        val jsonArray = JSONArray(termsOfServiceDataListString)
        for (i in 0 until jsonArray.length()) {
            TermsOfServiceData.from(jsonArray.getJSONObject(i))?.let {
                list.add(it)
            }
        }
        return list
    }

    private fun makeTermsOfServiceData(list: List<TermsOfServiceData>): JSONArray {
        val jsonArray = JSONArray()
        list.forEach {
            jsonArray.put(it.toJSONObject())
        }
        return jsonArray
    }

    private fun convert(jsonString: String): Map<String, String> {
        val map = mutableMapOf<String, String>()
        try {
            val jsonObject = JSONObject(jsonString)
            jsonObject.keys().forEach {
                map[it] = jsonObject.getString(it)
            }
        } catch (e: JSONException) {
            return emptyMap()
        }
        return map
    }

    @JvmStatic
    fun getVersion(): String? {
        val version : String? = com.stove.auth.BuildConfig.VERSION_NAME
        return version
    }

    @JvmStatic
    fun isMain(providerCode: String) : Boolean {
        Auth.accessToken?.user?.providerUsers?.forEach { providerUser: ProviderUser ->
            if (providerUser.code == providerCode) {
                if (providerUser.isMain) {
                    return true
                }
            }
        }
        return false
    }

    @JvmStatic
    fun setPlaytimeListener(identifier: String) {
        val context = UnityPlayer.currentActivity.applicationContext
        Auth.setPlayTimeListener(context) { data ->

            val jsonObject = JSONObject()
            try {
                jsonObject.put(IdentifierKey, identifier)                 
                
                val valueJSONObject = JSONObject()
                for ((key, value) in data) {
                    valueJSONObject.putIgnoreException(key, value)
                }

                jsonObject.putIgnoreException(ValueKey, valueJSONObject)
                
                val params = jsonObject.toString()

                UnityPlayer.UnitySendMessage(objectName, notifyFunctionName, params)
            } catch (e: Exception) {
                Logger.trace(
                    LogLevel.ERROR,
                    "$TAG.setPlaytimeListener",
                    exception = e
                )           
            }
        }
    }
}
