/*
 * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
 *
 * Copyright (c) 2015 - 2025 CCBlueX
 *
 * LiquidBounce is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LiquidBounce is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
 */
package net.ccbluex.liquidbounce.features.module.modules.exploit.disabler.disablers

import net.ccbluex.liquidbounce.config.AutoConfig
import net.ccbluex.liquidbounce.config.types.nesting.ToggleableConfigurable
import net.ccbluex.liquidbounce.event.events.PacketEvent
import net.ccbluex.liquidbounce.event.events.TransferOrigin
import net.ccbluex.liquidbounce.event.handler
import net.ccbluex.liquidbounce.features.module.modules.exploit.disabler.ModuleDisabler
import net.ccbluex.liquidbounce.lang.translation
import net.ccbluex.liquidbounce.utils.client.*
import net.minecraft.network.packet.c2s.common.CommonPongC2SPacket
import net.minecraft.network.packet.s2c.common.CommonPingS2CPacket
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket

/**
 * Ancient disabler method... but still works
 * On Grim you need a lot of extra logic for it to works.
 * I will try my best to explain everything.
 */
internal object DisablerGrimSpectate : ToggleableConfigurable(ModuleDisabler, "GrimSpectate", false) {

    private val packetQueue = LinkedHashSet<PacketSnapshot>()
    private var delay = false

    override fun onEnabled() {
        if (AutoConfig.loadingNow) {
            return
        }

        chat(warning(translation("liquidbounce.module.disabler.messages.grimSpectateEnableMessage")))
        super.onEnabled()
    }

    override fun onDisabled() {
        if (inGame) {
            packetQueue.forEach { handlePacket(it.packet) }
        }

        packetQueue.clear()
        delay = false
    }

    /**
     * This is simple... most AntiCheat use transaction to check if the packet it sent u have arrived.
     * So we are going to delay the transaction when we got fly abilities from dead spectator or etc...
     * to make the server think we haven't received the set your fly status to false
     * (after you respawn and not spectating anymore).
     * So we can disable the AntiCheat, since this could happen to some extreme laggy player,
     * it not really much proper, way to patch without affect the legits... That what we're going to abuse.
     * You get it, the idea is really stupid but works on old Intave and Grim
     * On Old Intave, player will not show up... on grim, it will flag reach which is make sense.
     * Grim think that you haven't received the player move packet so you for grim all player on our client-sided
     * it not moving and ofc that mean we are reaching or not even looking at the target.
     *
     * https://github.com/GrimAnticheat/Grim/blob/2.0/src/main/java/ac/grim/grimac/events/packets/PacketPlayerAbilities.java
     */
    @Suppress("unused")
    val packetHandler = handler<PacketEvent> { event ->
        val packet = event.packet

        if (player.age < 20) {
            packetQueue.clear()
            return@handler
        }

        if (event.origin != TransferOrigin.INCOMING) {
            return@handler
        }

        /**
         * THIS IS FOR GRIM....
         * Here come a bit tricky, after you not in spectator anymore, you got teleport back.
         * (I hate BadPacketsN). BadPacketsN check teleport queues every PlayerMoveC2SPacket.Full.
         *
         * When you release your transaction packets you delay
         * your lastTransaction data on Grim (or any) will go to the latest transaction.
         * But BadPacketsN check if the transaction use
         * for teleport is > your latest send transaction. Since u sent it like all in one,
         * it will not have time to check and wait for next tick that is PlayerMoveC2SPacket.Full
         * to check for BadPacketsN. But since your transaction is latest and your teleport
         * transaction is behind then it will flag
         *
         * Bypass BadPacketsN is not hard but... either way even if we bypass it, it will still set us back...
         * TODO: FIND A WAY AROUND THIS (works on all sv)
         *
         * ---------------------------------------------
         * Ok on some server we get teleport Packet before Abilities packet when we out of spectator,
         * how about we just delay right after teleport?
         * (please don't turn this on before you in spectator)
         * I'm not good at explaining stuff...
         * https://github.com/GrimAnticheat/Grim/blob/2.0/src/main/java/ac/grim/grimac/manager/SetbackTeleportUtil.java
         */
        if (packet is PlayerPositionLookS2CPacket) {
            // Should we start delaying?
            if (player.abilities.flying && !delay) {
                delay = true
            } else if (delay) {
                /**
                 * We flagged, info the player and add to packet queue to bypass BadPacketsN.
                 *
                 * This should not autotoggle, since everything that has been used
                 * with disabler would still be enabled.
                 */
                packetQueue.add(PacketSnapshot(packet, event.origin, System.currentTimeMillis()))
                chat(markAsError(translation("liquidbounce.module.disabler.messages.grimSpectateMessage")))
            }
        }

        if (delay) {
            // it's not really good idea to delay our packets for way too long, let's just release it after a while
            // idk this is fine for minemalia ig.
//                if (timer.hasElapsed(seconds.toLong() * 1000L)) {
//                    packetQueue.forEach() { handlePacket(it.packet) }
//                    packetQueue.clear()
//
//                    delay = false
//                    timer.reset()
//                }

            // delay transaction of course so the server will think we still have the flying ablities.
            if (packet is CommonPingS2CPacket) {
                packetQueue.add(PacketSnapshot(packet, event.origin, System.currentTimeMillis()))
                event.cancelEvent()

                // Prevent you from getting timed out, it will not work if your version is below 1.17
                network.sendPacket(CommonPongC2SPacket(0))
            }
        }
    }
}
