/*
 * 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.types.group.ToggleableValueGroup
import net.ccbluex.liquidbounce.event.events.BlinkPacketEvent
import net.ccbluex.liquidbounce.event.events.PacketEvent
import net.ccbluex.liquidbounce.event.events.TransferOrigin
import net.ccbluex.liquidbounce.event.events.WorldChangeEvent
import net.ccbluex.liquidbounce.event.handler
import net.ccbluex.liquidbounce.features.blink.BlinkManager
import net.ccbluex.liquidbounce.features.module.modules.exploit.disabler.ModuleDisabler
import net.ccbluex.liquidbounce.utils.text.asPlainText
import net.ccbluex.liquidbounce.utils.client.chat
import net.ccbluex.liquidbounce.utils.network.sendPacketSilently
import net.ccbluex.liquidbounce.utils.kotlin.EventPriorityConvention
import net.ccbluex.liquidbounce.utils.math.center
import net.minecraft.ChatFormatting
import net.minecraft.network.protocol.common.ServerboundKeepAlivePacket
import net.minecraft.network.protocol.common.ServerboundPongPacket
import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket
import net.minecraft.network.protocol.game.ServerboundAcceptTeleportationPacket
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket
import net.minecraft.network.protocol.game.ServerboundPlayerCommandPacket
import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket
import net.minecraft.world.phys.BlockHitResult

/**
 * Attempt at a Verus disabler, originally aimed at BlocksMC.
 * Instead of taking known disabler methods, this was made by referencing the Verus source code
 * (which should be 1:1 with the build BlocksMC is running) and looking for exemptions.
 *
 * A bit of credit to angles/port for tips of how older disablers worked.
 */
internal object DisablerVerusExperimental : ToggleableValueGroup(ModuleDisabler, "VerusExperimental", false) {

    private val worldChangeWait by int("WorldChangeWait", 15000, 0..30000, "ms")
    private val delayTime by int("DelayEndTime", 14000, 0..30000, "ms")
    private val positionDelayTime by int("DelayBeginTime", 13800, 0..30000, "ms")

    private val check14E by boolean("Scaffold14E", true)

    // Disables in-game check
    override val running: Boolean
        get() = ModuleDisabler.enabled && this.enabled

    object NoAction : ToggleableValueGroup(DisablerVerusExperimental, "NoAction", false) {

        private val sprinting by boolean("Sprinting", true)

        val packetHandler = handler<PacketEvent> { event ->
            if (event.packet is ServerboundPlayerCommandPacket) {
                when (event.packet.action) {

                    ServerboundPlayerCommandPacket.Action.START_SPRINTING,
                    ServerboundPlayerCommandPacket.Action.STOP_SPRINTING -> {
                        if (sprinting) {
                            event.cancelEvent()
                        }
                    }

                    else -> {}

                }
            }
        }

    }

    init {
        tree(VoidTP)
        tree(NoAction)
    }

    object VoidTP : ToggleableValueGroup(DisablerVerusExperimental, "VoidTP", false) {

        private val voidTPDelay by int("VoidTPDelay", 1000, 0..30000, "ms")

        private val waitForGround by boolean("WaitForGround", true)

        private var lastVoidTP = 0L
        private var cancelNext = 0
        private var lastTeleportId = 0

        val packetHandlerVoidTp = handler<PacketEvent> { event ->
            if (player.tickCount <= 20) return@handler

            if (event.packet is ServerboundMovePlayerPacket) {
                if (player.y > -64) {

                    if (lastVoidTP + voidTPDelay < System.currentTimeMillis() &&
                        (!waitForGround || player.onGround())) {
                        lastVoidTP = System.currentTimeMillis()

                        sendPacketSilently(
                            ServerboundMovePlayerPacket.PosRot(
                                player.x,
                                -48.0,
                                player.z,
                                player.yRot,
                                player.xRot,
                                true,
                                player.horizontalCollision
                            )
                        )

                        sendPacketSilently(
                            ServerboundMovePlayerPacket.PosRot(
                                player.xo,
                                player.yLast,
                                player.zo,
                                player.yRotO,
                                player.xRotO,
                                false,
                                player.horizontalCollision
                            )
                        )

                        sendPacketSilently(
                            ServerboundMovePlayerPacket.PosRot(
                                player.x,
                                player.y,
                                player.z,
                                player.yRot,
                                player.xRot,
                                player.onGround(),
                                player.horizontalCollision
                            )
                        )

                        sendPacketSilently(ServerboundAcceptTeleportationPacket(lastTeleportId))
                        chat("Teleport confirmed".asPlainText(ChatFormatting.GREEN))
                        event.cancelEvent()
                        cancelNext = 2
                    }

                }
            }

            if (event.packet is ClientboundPlayerPositionPacket) {
                chat("Position reset received".asPlainText(ChatFormatting.RED))
                if (cancelNext <= 0) {
                    return@handler
                }
                cancelNext--

                event.cancelEvent()
                lastTeleportId = event.packet.id

                chat("Position reset cancelled".asPlainText(ChatFormatting.GREEN))
            }
        }

    }

    private var lastFlush = 0L

    private var waitTime = 0L

    private var lastDiff = -1L
    private var lastPing = 1L

    @Suppress("unused")
    private val worldChangeHandler = handler<WorldChangeEvent> {
        waitTime = System.currentTimeMillis() + worldChangeWait
    }

    @Suppress("unused")
    private val packetHandler = handler<PacketEvent> { event ->
        when (val packet = event.packet) {
            is ServerboundUseItemOnPacket -> {
                if (check14E) {
                    sendPacketSilently(
                        ServerboundUseItemOnPacket(
                            packet.hand, BlockHitResult(
                                packet.hitResult.blockPos.center,
                                packet.hitResult.direction,
                                packet.hitResult.blockPos,
                                packet.hitResult.isInside
                            ), packet.sequence
                        )
                    )

                    event.cancelEvent()
                }
            }
        }
    }

    @Suppress("unused")
    private val fakeLagHandler = handler<BlinkPacketEvent>(
        priority = EventPriorityConvention.SAFETY_FEATURE
    ) { event ->
        if (event.origin != TransferOrigin.OUTGOING || waitTime > System.currentTimeMillis()) {
            return@handler
        }

        val packet = event.packet

        event.action = when (packet) {
            null if lastFlush + delayTime < System.currentTimeMillis() -> {
                lastFlush = System.currentTimeMillis()
                BlinkManager.Action.QUEUE
            }
            is ServerboundKeepAlivePacket -> {
                if (lastDiff == -1L) {
                    lastDiff = packet.id
                } else {
                    if (packet.id != lastPing) {
                        lastPing = lastDiff
                        lastDiff = packet.id
                        packet.id = lastPing
                    }
                }

                BlinkManager.Action.QUEUE
            }

            is ServerboundPongPacket -> {
                BlinkManager.Action.QUEUE
            }

            else -> return@handler
        }

    }

    @Suppress("unused")
    private val flushHandler = handler<BlinkPacketEvent>(
        priority = EventPriorityConvention.OBJECTION_AGAINST_EVERYTHING
    ) { event ->
        val packet = event.packet

        if (waitTime > System.currentTimeMillis() || event.origin != TransferOrigin.OUTGOING
            || packet !is ServerboundMovePlayerPacket
        ) {
            return@handler
        }

        if (lastFlush + positionDelayTime < System.currentTimeMillis()) {
            event.action = BlinkManager.Action.QUEUE
        }
    }

}
