package zenith.zov.client.modules.impl.combat;

import com.darkmagician6.eventapi.EventTarget;
import java.util.Iterator;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import net.minecraft.client.render.Frustum;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket;
import net.minecraft.network.packet.s2c.play.EntityS2CPacket;
import net.minecraft.network.packet.s2c.play.EntityVelocityUpdateS2CPacket;
import net.minecraft.network.packet.s2c.play.EntityS2CPacket.MoveRelative;
import net.minecraft.network.packet.s2c.play.EntityS2CPacket.Rotate;
import net.minecraft.network.packet.s2c.play.EntityS2CPacket.RotateAndMoveRelative;
import net.minecraft.util.math.Vec3d;
import zenith.zov.base.events.impl.player.EventUpdate;
import zenith.zov.base.events.impl.render.EventRender3D;
import zenith.zov.base.events.impl.server.EventPacket;
import zenith.zov.client.modules.api.Category;
import zenith.zov.client.modules.api.Module;
import zenith.zov.client.modules.api.ModuleAnnotation;
import zenith.zov.client.modules.api.setting.impl.BooleanSetting;
import zenith.zov.client.modules.api.setting.impl.ModeSetting;
import zenith.zov.client.modules.api.setting.impl.NumberSetting;
import zenith.zov.utility.animation.PositionAnimation;
import zenith.zov.utility.entity.FakePlayerEntity;

@ModuleAnnotation(
    name = "BackTrack",
    category = Category.COMBAT,
    description = "Abuses lag compensation to gain additional reach"
)
public class BackTrack extends Module {
    public static final BackTrack INSTANCE = new BackTrack();
    private final ModeSetting mode = new ModeSetting("Mode", "Dynamic", "Dynamic", "Static", "Sandevistan");
    private final NumberSetting delay = new NumberSetting("Delay", 50.0F, 0.0F, 2000.0F, 1.0F, () -> !this.mode.get().equals("Sandevistan"));
    private final ModeSetting clearMode = new ModeSetting("Clear Mode", () -> this.mode.get().equals("Sandevistan"), "Distance", "Time");
    private final NumberSetting clearLength = new NumberSetting(
        "History Timeout", 300.0F, 100.0F, 2000.0F, 1.0F, () -> this.clearMode.get().equals("Time") && this.mode.get().equals("Sandevistan")
    );
    private final NumberSetting distanceLength = new NumberSetting(
        "Distance Threshold", 6.0F, 1.0F, 10.0F, 0.1F, () -> this.clearMode.get().equals("Distance") && this.mode.get().equals("Sandevistan")
    );
    private final BooleanSetting render = new BooleanSetting("Render", true);
    private final BooleanSetting alpha = new BooleanSetting("Alpha", true, () -> this.mode.get().equals("Sandevistan"));
    private final Queue<BackTrack.PacketData> packets = new LinkedBlockingQueue<>();
    private final LinkedBlockingQueue<BackTrack.PositionRecord> positionHistory = new LinkedBlockingQueue<>();
    private PlayerEntity trackedEntity;
    private Vec3d currentPos = Vec3d.ZERO;
    private Frustum frustum;
    private final PositionAnimation positionAnimation;
    private Vec3d lastPos = Vec3d.ZERO;
    private double distance;
    private int approachingTicks = 0;

    public BackTrack() {
        this.positionAnimation = new PositionAnimation(300.0F);
    }

    @Override
    public void onEnable() {
        super.onEnable();
        this.cleanUp();
    }

    @EventTarget
    public void onPacket(EventPacket event) {
        if (event.isReceive() && mc.world != null && mc.player != null && this.trackedEntity != null) {
            boolean cancel = false;
            Vec3d newPos = null;
            if (event.getPacket() instanceof EntityS2CPacket entityPacket) {
                Entity entity = entityPacket.getEntity(mc.world);
                if (entity == this.trackedEntity) {
                    Objects.requireNonNull(entityPacket);
                    switch (entityPacket) {
                        case MoveRelative moveRelativePacket:
                            newPos = this.currentPos
                                .add(
                                    (double)moveRelativePacket.getDeltaX() / 4096.0,
                                    (double)moveRelativePacket.getDeltaY() / 4096.0,
                                    (double)moveRelativePacket.getDeltaZ() / 4096.0
                                );
                            cancel = true;
                            break;
                        case RotateAndMoveRelative rotateAndMoveRelativePacket:
                            newPos = this.currentPos
                                .add(
                                    (double)rotateAndMoveRelativePacket.getDeltaX() / 4096.0,
                                    (double)rotateAndMoveRelativePacket.getDeltaY() / 4096.0,
                                    (double)rotateAndMoveRelativePacket.getDeltaZ() / 4096.0
                                );
                            cancel = true;
                            break;
                        case Rotate ignored:
                            cancel = true;
                            newPos = this.currentPos;
                            break;
                    }
                }
            } else if (event.getPacket() instanceof EntityVelocityUpdateS2CPacket packet && packet.getEntityId() == this.trackedEntity.getId()) {
                cancel = true;
            }

            if (cancel && newPos != null) {
                this.currentPos = newPos;
                this.positionAnimation.animate(newPos);
                if (this.mode.get().equals("Sandevistan")) {
                    FakePlayerEntity sandevistanClone = new FakePlayerEntity(this.trackedEntity, "", true);
                    BackTrack.PositionRecord record = new BackTrack.PositionRecord(sandevistanClone, System.currentTimeMillis());
                    this.positionHistory.add(record);
                    record.entity().spawnPlayer();
                } else {
                    this.packets.add(new BackTrack.PacketData(event.getPacket(), System.currentTimeMillis()));
                    event.cancel();
                }
            }
        }
    }

    @EventTarget
    public void onUpdate(EventUpdate event) {
        if (mc.player != null && mc.world != null) {
            if (mc.player.age > 10 && this.trackedEntity != null && !this.mode.get().equals("Sandevistan")) {
                if (this.lastPos.equals(Vec3d.ZERO)) {
                    this.lastPos = this.currentPos;
                }

                Vec3d targetVelocity = this.currentPos.subtract(this.lastPos);
                Vec3d toTarget = this.currentPos.subtract(mc.player.getPos());
                boolean isApproaching = !targetVelocity.equals(Vec3d.ZERO)
                    && toTarget.lengthSquared() > 0.001
                    && targetVelocity.normalize().dotProduct(toTarget.normalize()) < -0.01;
                this.distance = mc.player.squaredDistanceTo(this.currentPos);
                String var5 = this.mode.get();
                switch (var5) {
                    case "Static":
                        this.applyPackets(false);
                        break;
                    case "Dynamic":
                        if (isApproaching) {
                            this.approachingTicks++;
                        } else {
                            this.approachingTicks = Math.max(0, this.approachingTicks - 1);
                        }

                        if (this.approachingTicks <= 1 && !(this.distance > 36.0)) {
                            this.applyPackets(false);
                        } else {
                            this.applyPackets(true);
                            this.approachingTicks = 0;
                        }
                }

                this.lastPos = this.currentPos;
            }
        }
    }

    @EventTarget
    public void onPacketSend(EventPacket event) {
        if (event.isSent() && event.getPacket() instanceof PlayerInteractEntityC2SPacket packet) {
            if (this.trackedEntity != null) {
                this.currentPos = this.trackedEntity.getPos();
                this.positionAnimation.setInstant(this.currentPos);
            }
        }
    }

    @EventTarget
    public void onRender3D(EventRender3D event) {
        if (this.mode.get().equals("Sandevistan")) {
            if (this.clearMode.get().equals("Distance")) {
                Iterator<BackTrack.PositionRecord> iterator = this.positionHistory.iterator();

                while (iterator.hasNext()) {
                    BackTrack.PositionRecord p = iterator.next();
                    if (this.trackedEntity != null
                        && p.entity().getPos().distanceTo(this.trackedEntity.getPos()) > (double)this.distanceLength.getCurrent()) {
                        p.entity().despawnPlayer();
                        iterator.remove();
                    }
                }
            } else {
                long cutoff = System.currentTimeMillis() - (long)this.clearLength.getCurrent();
                Iterator<BackTrack.PositionRecord> iterator = this.positionHistory.iterator();

                while (iterator.hasNext()) {
                    BackTrack.PositionRecord p = iterator.next();
                    if (p.timestamp() < cutoff) {
                        p.entity().despawnPlayer();
                        iterator.remove();
                    }
                }
            }
        }

        if (this.render.isEnabled() && this.trackedEntity != null) {
            if (this.frustum == null || this.frustum.isVisible(this.trackedEntity.getBoundingBox())) {
                MatrixStack matrixStack = event.getMatrix();
                matrixStack.push();
                float halfWidth = this.trackedEntity.getWidth() / 2.0F;
                float height = this.trackedEntity.getHeight();
                if (!this.mode.get().equals("Sandevistan")) {
                    Vec3d var10 = this.positionAnimation.getCurrentPos();
                }

                matrixStack.pop();
            }
        }
    }

    public static Vec3d interpolate(Vec3d from, Vec3d to, float tickDelta) {
        double x = from.x + (to.x - from.x) * (double)tickDelta;
        double y = from.y + (to.y - from.y) * (double)tickDelta;
        double z = from.z + (to.z - from.z) * (double)tickDelta;
        return new Vec3d(x, y, z);
    }

    private void applyPackets(boolean force) {
        float val = this.delay.getCurrent();
        this.packets.removeIf(packetData -> {
            if (!((float)(System.currentTimeMillis() - packetData.time()) > val) && !force) {
                return false;
            } else {
                try {
                    Packet patt0$temp = packetData.packet();
                    if (patt0$temp instanceof Packet) {
                        patt0$temp.apply(mc.getNetworkHandler());
                    }
                } catch (ClassCastException var5) {
                }

                return true;
            }
        });
    }

    @Override
    public void onDisable() {
        super.onDisable();
        this.cleanUp();
    }

    private void cleanUp() {
        this.applyPackets(true);
        this.packets.clear();
        this.positionHistory.forEach(p -> p.entity().despawnPlayer());
        this.positionHistory.clear();
        this.trackedEntity = null;
        this.currentPos = Vec3d.ZERO;
        this.positionAnimation.setInstant(Vec3d.ZERO);
        this.lastPos = Vec3d.ZERO;
        this.approachingTicks = 0;
    }

    private static record PacketData(Packet<?> packet, long time) {
    }

    private static record PositionRecord(FakePlayerEntity entity, long timestamp) {
    }
}
