package tk.zwander.common.util

import android.content.Intent
import android.net.Uri
import androidx.core.net.toUri
import com.google.gson.*
import com.google.gson.reflect.TypeToken
import tk.zwander.common.data.SafePointF
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type

class CrashFixExclusionStrategy : ExclusionStrategy {
    private val fieldsToAvoid: Set<String> = [
        "IS_ELASTIC_ENABLED",
        "isElasticEnabled"
    ]

    override fun shouldSkipClass(clazz: Class<*>?): Boolean {
        return false
    }

    override fun shouldSkipField(fieldAttributes: FieldAttributes): Boolean {
        val fieldName = fieldAttributes.name

        return fieldsToAvoid.contains(fieldName)
    }
}

class GsonUriHandler : JsonDeserializer<Uri?>, JsonSerializer<Uri?> {
    override fun deserialize(
        src: JsonElement?,
        srcType: Type,
        context: JsonDeserializationContext?,
    ): Uri? {
        return src?.let {
            try {
                src.asString.toUri()
            } catch (_: Exception) {
                null
            }
        }
    }

    override fun serialize(src: Uri?, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
        return JsonPrimitive(src?.toString())
    }
}

class GsonIntentHandler : JsonSerializer<Intent?>, JsonDeserializer<Intent?> {
    override fun serialize(src: Intent?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
        return JsonPrimitive(src?.toUri(0))
    }

    override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Intent? {
        return json?.let {
            try {
                Intent.parseUri(json.asString, 0)
            } catch (_: Exception) {
                null
            }
        }
    }
}

class GsonSafePointFHandler : JsonSerializer<SafePointF?>, JsonDeserializer<SafePointF?> {
    override fun serialize(
        src: SafePointF?,
        typeOfSrc: Type?,
        context: JsonSerializationContext?,
    ): JsonElement {
        return JsonObject().apply {
            this.addProperty("x", src?.x)
            this.addProperty("y", src?.y)
        }
    }

    override fun deserialize(
        json: JsonElement?,
        typeOfT: Type?,
        context: JsonDeserializationContext?,
    ): SafePointF? {
        val obj = json?.asJsonObject ?: return null

        return SafePointF(obj.get("x").asFloat, obj.get("y").asFloat)
    }
}

class GsonPairHandler : JsonSerializer<Pair<*, *>?>, JsonDeserializer<Pair<*, *>?> {
    override fun serialize(
        src: Pair<*, *>?,
        typeOfSrc: Type?,
        context: JsonSerializationContext?,
    ): JsonElement? {
        return src?.toString()?.let { JsonPrimitive(it) }
    }

    override fun deserialize(
        json: JsonElement?,
        typeOfT: Type?,
        context: JsonDeserializationContext,
    ): Pair<*, *>? {
        if (json?.isJsonPrimitive == true) {
            val obj = json.asJsonPrimitive?.asString ?: return null
            val realType = typeOfT as ParameterizedType
            val aType = realType.actualTypeArguments[0]
            val bType = realType.actualTypeArguments[1]
            val stripped = obj.replace("(", "")
                .replace(")", "")
            val [a, b] = stripped.split(", ")

            return Pair(
                context.deserialize<Any>(JsonPrimitive(a), aType),
                context.deserialize<Any>(JsonPrimitive(b), bType),
            )
        } else {
            val obj = json?.asJsonObject ?: return null
            val realType = typeOfT as ParameterizedType
            val aType = realType.actualTypeArguments[0]
            val bType = realType.actualTypeArguments[1]

            val a = obj.get("first")
            val b = obj.get("second")

            return Pair(
                context.deserialize<Any>(a, aType),
                context.deserialize<Any>(b, bType),
            )
        }
    }
}

// https://stackoverflow.com/a/56388957
inline fun <reified T> Gson.safeFromJson(value: String?): T? {
    val typeToken = object : TypeToken<T>() {}

    return try {
        fromJson(value, typeToken)
    } catch (e: JsonSyntaxException) {
        if (e.message?.contains("duplicate key") == true) {
            // Reserialize object if it has duplicate keys, keeping only the last
            // instance of the key.
            fromJson(
                toJson(fromJson(value, object : TypeToken<Any>() {})),
                typeToken,
            )
        } else {
            throw e
        }
    }
}

inline fun <reified K, reified V> Gson.mapFromJson(value: String?): HashMap<K, V> {
    return safeFromJson(value) ?: HashMap()
}
