package app.revanced.patches.spotify.misc.lyrics

import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
import app.revanced.patcher.classDef
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.methodReference
import app.revanced.patcher.extensions.replaceInstruction
import app.revanced.patcher.firstMethodDeclaratively
import app.revanced.patcher.instructions
import app.revanced.patcher.method
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.returnType
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
import java.net.InetAddress
import java.net.URI
import java.net.URISyntaxException
import java.net.UnknownHostException
import java.util.logging.Logger

@Suppress("unused")
val changeLyricsProviderPatch = bytecodePatch(
    name = "Change lyrics provider",
    description = "Changes the lyrics provider to a custom one.",
    use = false,
) {
    compatibleWith("com.spotify.music")

    val lyricsProviderHost by stringOption(
        default = "lyrics.natanchiodi.fr",
        name = "Lyrics provider host",
        description = "The domain name or IP address of a custom lyrics provider.",
        required = false,
    ) {
        // Fix bad data if the user enters a URL (https://whatever.com/path).
        val host = try {
            URI(it!!).host ?: it
        } catch (e: URISyntaxException) {
            return@stringOption false
        }

        // Do a courtesy check if the host can be resolved.
        // If it does not resolve, then print a warning but use the host anyway.
        // Unresolvable hosts should not be rejected, since the patching environment
        // may not allow network connections or the network may be down.
        try {
            InetAddress.getByName(host)
        } catch (_: UnknownHostException) {
            Logger.getLogger(this::class.java.name).warning(
                "Host \"$host\" did not resolve to any domain.",
            )
        } catch (_: Exception) {
            // Must ignore any kind of exception. Trying to resolve network
            // on Manager throws android.os.NetworkOnMainThreadException
        }

        true
    }

    apply {
        // region Create a modified copy of the HTTP client builder method with the custom lyrics provider host.

        val patchedHttpClientBuilderMethod = with(httpClientBuilderMethod) {
            val invokeBuildUrlIndex = indexOfFirstInstructionOrThrow {
                methodReference?.returnType == "Lokhttp3/HttpUrl;"
            }
            val setUrlBuilderHostIndex = indexOfFirstInstructionReversedOrThrow(invokeBuildUrlIndex) {
                val reference = methodReference
                reference?.definingClass == "Lokhttp3/HttpUrl${"$"}Builder;" &&
                    reference.parameterTypes.firstOrNull() == "Ljava/lang/String;"
            }
            val hostRegister = getInstruction<FiveRegisterInstruction>(setUrlBuilderHostIndex).registerD

            MutableMethod(this).apply {
                name = "rv_getCustomLyricsProviderHttpClient"
                addInstruction(
                    setUrlBuilderHostIndex,
                    "const-string v$hostRegister, \"$lyricsProviderHost\"",
                )

                // Add the patched method to the class.
                httpClientBuilderMethod.classDef.methods.add(this)
            }
        }

        //endregion

        // region Replace the call to the HTTP client builder method used exclusively for lyrics by the modified one.

        val getLyricsHttpClientMethod = firstMethodDeclaratively {
            returnType(httpClientBuilderMethod.returnType)
            parameterTypes()
            instructions(method { this == httpClientBuilderMethod })
        }

        getLyricsHttpClientMethod.apply {
            val getLyricsHttpClientIndex = indexOfFirstInstructionOrThrow {
                methodReference == httpClientBuilderMethod
            }
            val getLyricsHttpClientInstruction = getInstruction<BuilderInstruction35c>(getLyricsHttpClientIndex)

            // Replace the original method call with a call to our patched method.
            replaceInstruction(
                getLyricsHttpClientIndex,
                BuilderInstruction35c(
                    getLyricsHttpClientInstruction.opcode,
                    getLyricsHttpClientInstruction.registerCount,
                    getLyricsHttpClientInstruction.registerC,
                    getLyricsHttpClientInstruction.registerD,
                    getLyricsHttpClientInstruction.registerE,
                    getLyricsHttpClientInstruction.registerF,
                    getLyricsHttpClientInstruction.registerG,
                    ImmutableMethodReference(
                        patchedHttpClientBuilderMethod.definingClass,
                        patchedHttpClientBuilderMethod.name, // Only difference from the original method.
                        patchedHttpClientBuilderMethod.parameters,
                        patchedHttpClientBuilderMethod.returnType,
                    ),
                ),
            )
        }

        //endregion
    }
}
