package app.revanced.patches.youtube.misc.imageurlhook

import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
import app.revanced.patcher.classDef
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.instructions
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.util.getReference
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod

private lateinit var loadImageURLMethod: MutableMethod
private var loadImageURLIndex = 0

private lateinit var loadImageSuccessCallbackMethod: MutableMethod
private var loadImageSuccessCallbackIndex = 0

private lateinit var loadImageErrorCallbackMethod: MutableMethod
private var loadImageErrorCallbackIndex = 0

val cronetImageURLHookPatch = bytecodePatch(
    description = "Hooks Cronet image URLs.",
) {
    dependsOn(sharedExtensionPatch)

    apply {

        loadImageURLMethod = messageDigestImageURLParentMethod.immutableClassDef.getMessageDigestImageURLMethod()
        loadImageSuccessCallbackMethod = onResponseStartedMethod.immutableClassDef.getOnSucceededMethod()
        loadImageErrorCallbackMethod = onResponseStartedMethod.immutableClassDef.getOnFailureMethod()

        // The URL is required for the failure callback hook, but the URL field is obfuscated.
        // Add a helper get method that returns the URL field.
        val urlFieldInstruction = requestMethod.instructions.first {
            val reference = it.getReference<FieldReference>()
            it.opcode == Opcode.IPUT_OBJECT && reference?.type == "Ljava/lang/String;"
        } as ReferenceInstruction

        val urlFieldName = (urlFieldInstruction.reference as FieldReference).name
        val definingClass = CRONET_URL_REQUEST_CLASS_DESCRIPTOR
        val addedMethodName = "getHookedUrl"
        requestMethod.classDef.methods.add(
            ImmutableMethod(
                definingClass,
                addedMethodName,
                emptyList(),
                "Ljava/lang/String;",
                AccessFlags.PUBLIC.value,
                null,
                null,
                MutableMethodImplementation(2),
            ).toMutable().apply {
                addInstructions(
                    """
                        iget-object v0, p0, $definingClass->$urlFieldName:Ljava/lang/String;
                        return-object v0
                    """,
                )
            },
        )
    }
}

/**
 * @param highPriority If the hook should be called before all other hooks.
 */
fun addImageURLHook(targetMethodClass: String, highPriority: Boolean = false) {
    loadImageURLMethod.addInstructions(
        if (highPriority) 0 else loadImageURLIndex,
        """
        invoke-static { p1 }, $targetMethodClass->overrideImageURL(Ljava/lang/String;)Ljava/lang/String;
        move-result-object p1
        """,
    )
    loadImageURLIndex += 2
}

/**
 * If a connection completed, which includes normal 200 responses but also includes
 * status 404 and other error like http responses.
 */
fun addImageURLSuccessCallbackHook(targetMethodClass: String) {
    loadImageSuccessCallbackMethod.addInstruction(
        loadImageSuccessCallbackIndex++,
        "invoke-static { p1, p2 }, $targetMethodClass->handleCronetSuccess(" +
            "Lorg/chromium/net/UrlRequest;Lorg/chromium/net/UrlResponseInfo;)V",
    )
}

/**
 * If a connection outright failed to complete any connection.
 */
fun addImageURLErrorCallbackHook(targetMethodClass: String) {
    loadImageErrorCallbackMethod.addInstruction(
        loadImageErrorCallbackIndex++,
        "invoke-static { p1, p2, p3 }, $targetMethodClass->handleCronetFailure(" +
            "Lorg/chromium/net/UrlRequest;Lorg/chromium/net/UrlResponseInfo;Ljava/io/IOException;)V",
    )
}
