/*
 * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
 *
 * Copyright (c) 2015 - 2026 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.AutoConfig
import net.ccbluex.liquidbounce.config.types.group.ToggleableValueGroup
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.blink.PacketSnapshot
import net.ccbluex.liquidbounce.features.module.modules.exploit.disabler.ModuleDisabler
import net.ccbluex.liquidbounce.lang.translation
import net.ccbluex.liquidbounce.utils.client.chat
import net.ccbluex.liquidbounce.utils.network.handlePacket
import net.ccbluex.liquidbounce.utils.client.inGame
import net.ccbluex.liquidbounce.utils.client.markAsError
import net.ccbluex.liquidbounce.utils.client.warning
import net.minecraft.network.protocol.common.ClientboundPingPacket
import net.minecraft.network.protocol.common.ServerboundPongPacket
import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket

/**
 * Ancient disabler method... but still works
 * On Grim you need a lot of extra logic for it to work.
 */
internal object DisablerGrimSpectate : ToggleableValueGroup(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 anticheats use a transaction to check if the packet it sent you has arrived.
     * So we are going to delay transactions when we gain fly abilities from dying in bedwars
     * to make the server think we haven't gotten the packet that sets our fly status to false
     * With this, we can temporarily disable movement checks, as this could happen to a laggy player.
     *
     * You get it, the idea is really stupid but works on old Intave and Grim
     * On old Intave, the player will not show up. On grim, it will disable combat which makes sense, as you
     * have not received any information about other players.
     *
     * 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.tickCount < 20) {
            packetQueue.clear()
            return@handler
        }

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

        /**
         * THIS IS FOR GRIM....
         * This is a bit tricky, after you aren't in spectator anymore, you get teleported back.
         * BadPacketsN checks teleport queues for 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 you sent it at the same time,
         * 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
         *
         * Bypassing BadPacketsN isn't 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)
         *
         * ---------------------------------------------
         * On some servers, we get teleported before the abilities packet. We could delay after
         * teleporting, but that wouldn't be an universal fix.
         *
         * 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 ClientboundPlayerPositionPacket) {
            // 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 a 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 so the server will think we still have flying abilities
            if (packet is ClientboundPingPacket) {
                packetQueue.add(PacketSnapshot(packet, event.origin, System.currentTimeMillis()))
                event.cancelEvent()

                // Prevents you from getting timed out, it will not work if your client version is below 1.17
                network.send(ServerboundPongPacket(0))
            }
        }
    }
}
