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

import com.darkmagician6.eventapi.EventTarget;
import java.awt.Color;
import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity;
import net.minecraft.entity.decoration.EndCrystalEntity;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.entity.mob.SlimeEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.hit.HitResult.Type;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import org.lwjgl.glfw.GLFW;
import zenith.zov.base.events.impl.player.EventUpdate;
import zenith.zov.base.events.impl.render.EventRender3D;
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.ColorSetting;
import zenith.zov.client.modules.api.setting.impl.NumberSetting;
import zenith.zov.utility.interfaces.IMinecraft;
import zenith.zov.utility.render.display.base.color.ColorRGBA;
import zenith.zov.utility.render.level.Render3DUtil;

@ModuleAnnotation(
    name = "AutoCrystal",
    category = Category.CRYSTAL,
    description = "Automatically crystals fast for you"
)
public final class AutoCrystal extends Module implements IMinecraft {
    public static final AutoCrystal INSTANCE = new AutoCrystal();
    private final NumberSetting placeDelay = new NumberSetting("Place Delay", 0.0F, 0.0F, 20.0F, 1.0F);
    private final NumberSetting breakDelay = new NumberSetting("Break Delay", 0.0F, 0.0F, 20.0F, 1.0F);
    private final BooleanSetting stopOnKill = new BooleanSetting("Stop on Kill", false);
    private final BooleanSetting damageTick = new BooleanSetting("Damage Tick", false);
    private final BooleanSetting antiWeakness = new BooleanSetting("Anti-Weakness", false);
    private final BooleanSetting render = new BooleanSetting("Render", true);
    private final ColorSetting color = new ColorSetting("Color", new ColorRGBA(124, 126, 238, 255));
    private final BooleanSetting glow = new BooleanSetting("Glow", true);
    private final NumberSetting glowIntensity = new NumberSetting("Glow Intensity", 3.0F, 1.0F, 10.0F, 0.5F);
    private int placeClock;
    private int breakClock;
    public boolean crystalling;
    private BlockPos currentObsidianPos = null;
    private BlockPos targetObsidianPos = null;
    private BlockPos lastObsidianPos = null;
    private boolean isFadingOut = false;
    private long fadeOutStartTime = 0L;
    private static final long FADE_OUT_DURATION = 2000L;
    private float fadeOutFactor = 1.0F;
    private double renderX = 0.0;
    private double renderY = 0.0;
    private double renderZ = 0.0;
    private static final double RENDER_SPEED = 0.12;
    private long lastRenderUpdate = 0L;

    private AutoCrystal() {
    }

    @Override
    public void onEnable() {
        this.placeClock = 0;
        this.breakClock = 0;
        this.crystalling = false;
        this.currentObsidianPos = null;
        this.targetObsidianPos = null;
        this.renderX = 0.0;
        this.renderY = 0.0;
        this.renderZ = 0.0;
        this.lastRenderUpdate = 0L;
        super.onEnable();
    }

    @Override
    public void onDisable() {
        this.currentObsidianPos = null;
        this.targetObsidianPos = null;
        this.renderX = 0.0;
        this.renderY = 0.0;
        this.renderZ = 0.0;
        this.lastRenderUpdate = 0L;
        super.onDisable();
    }

    @EventTarget
    public void onUpdate(EventUpdate event) {
        if (mc.currentScreen == null) {
            this.updateFadeOutFactor();
            if (this.render.isEnabled()) {
                if (this.isLookingAtObsidian()) {
                    HitResult hitResult = mc.crosshairTarget;
                    if (hitResult != null && hitResult.getType() == Type.BLOCK) {
                        BlockHitResult blockHit = (BlockHitResult)hitResult;
                        this.targetObsidianPos = blockHit.getBlockPos();
                        this.lastObsidianPos = this.targetObsidianPos;
                        this.isFadingOut = false;
                        this.fadeOutFactor = 1.0F;
                    } else if (hitResult != null && hitResult.getType() == Type.ENTITY) {
                        EntityHitResult entityHit = (EntityHitResult)hitResult;
                        if (entityHit.getEntity() instanceof EndCrystalEntity crystal) {
                            BlockPos blockPos = new BlockPos(
                                (int)Math.floor(crystal.getX()), (int)Math.floor(crystal.getY()) - 1, (int)Math.floor(crystal.getZ())
                            );
                            this.targetObsidianPos = blockPos;
                            this.lastObsidianPos = blockPos;
                            this.isFadingOut = false;
                            this.fadeOutFactor = 1.0F;
                        }
                    }
                } else if (this.targetObsidianPos != null && !this.isFadingOut) {
                    this.isFadingOut = true;
                    this.fadeOutStartTime = System.currentTimeMillis();
                }
            }

            boolean dontPlace = this.placeClock != 0;
            boolean dontBreak = this.breakClock != 0;
            if (!this.stopOnKill.isEnabled() || !this.isDeadBodyNearby()) {
                if (dontPlace) {
                    this.placeClock--;
                }

                if (dontBreak) {
                    this.breakClock--;
                }

                if (!mc.player.isUsingItem()) {
                    if (!this.damageTick.isEnabled() || !this.damageTickCheck()) {
                        if (GLFW.glfwGetMouseButton(mc.getWindow().getHandle(), 1) != 1) {
                            this.placeClock = 0;
                            this.breakClock = 0;
                            this.crystalling = false;
                        } else {
                            this.crystalling = true;
                            if (mc.player.getMainHandStack().isOf(Items.END_CRYSTAL)) {
                                if (mc.crosshairTarget instanceof BlockHitResult hit) {
                                    if (mc.crosshairTarget.getType() == Type.BLOCK) {
                                        this.handleBlockHit(hit, dontPlace, dontBreak);
                                    }

                                    if (mc.crosshairTarget.getType() == Type.MISS) {
                                        this.handleMissHit(dontPlace, dontBreak);
                                    }
                                }

                                if (mc.crosshairTarget instanceof EntityHitResult hit) {
                                    this.handleEntityHit(hit, dontBreak);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private void handleBlockHit(BlockHitResult hit, boolean dontPlace, boolean dontBreak) {
        if (!dontPlace && this.isObsidianOrBedrock(hit.getBlockPos()) && this.canPlaceCrystal(hit.getBlockPos())) {
            this.placeBlock(hit);
            this.placeClock = (int)this.placeDelay.getCurrent();
        }
    }

    private void handleMissHit(boolean dontPlace, boolean dontBreak) {
    }

    private void handleEntityHit(EntityHitResult hit, boolean dontBreak) {
        if (!dontBreak) {
            Entity entity = hit.getEntity();
            int previousSlot = mc.player.getInventory().selectedSlot;
            if ((entity instanceof EndCrystalEntity || entity instanceof SlimeEntity) && this.antiWeakness.isEnabled() && this.cantBreakCrystal()) {
                this.selectSword();
            }

            this.attackEntity(entity);
            this.breakClock = (int)this.breakDelay.getCurrent();
            if (this.antiWeakness.isEnabled()) {
                mc.player.getInventory().selectedSlot = previousSlot;
            }
        }
    }

    private boolean isDeadBodyNearby() {
        if (mc.world != null && mc.player != null) {
            double radius = 10.0;
            return mc.world
                .getPlayers()
                .stream()
                .filter(player -> player != mc.player)
                .filter(player -> player.isDead() || player.getHealth() <= 0.0F)
                .anyMatch(player -> mc.player.squaredDistanceTo(player) <= radius * radius);
        } else {
            return false;
        }
    }

    private boolean isObsidianOrBedrock(BlockPos pos) {
        return mc.world.getBlockState(pos).getBlock() == Blocks.OBSIDIAN || mc.world.getBlockState(pos).getBlock() == Blocks.BEDROCK;
    }

    private boolean canPlaceCrystal(BlockPos pos) {
        if (mc.world == null) {
            return false;
        } else {
            BlockPos crystalPos = pos.up();
            if (!mc.world.getBlockState(crystalPos).isAir()) {
                return false;
            } else {
                return !mc.world.getBlockState(crystalPos.up()).isAir()
                    ? false
                    : mc.world.getEntitiesByClass(Entity.class, new Box(crystalPos), entity -> !entity.isSpectator()).isEmpty();
            }
        }
    }

    private void placeBlock(BlockHitResult hit) {
        if (mc.interactionManager != null) {
            mc.interactionManager.interactBlock(mc.player, mc.player.getActiveHand(), hit);
        }
    }

    private void attackEntity(Entity entity) {
        if (mc.interactionManager != null) {
            mc.interactionManager.attackEntity(mc.player, entity);
            mc.player.swingHand(Hand.MAIN_HAND);
        }
    }

    private void selectSword() {
        for (int i = 0; i < 9; i++) {
            if (mc.player.getInventory().getStack(i).getItem().toString().contains("sword")) {
                mc.player.getInventory().selectedSlot = i;
                break;
            }
        }
    }

    private boolean cantBreakCrystal() {
        if (mc.player == null) {
            return false;
        } else {
            StatusEffectInstance weakness = mc.player.getStatusEffect(StatusEffects.WEAKNESS);
            StatusEffectInstance strength = mc.player.getStatusEffect(StatusEffects.STRENGTH);
            return weakness != null && (strength == null || strength.getAmplifier() <= weakness.getAmplifier()) && !this.isTool(mc.player.getMainHandStack());
        }
    }

    private boolean isTool(ItemStack stack) {
        return stack.getItem().toString().contains("sword")
            || stack.getItem().toString().contains("axe")
            || stack.getItem().toString().contains("pickaxe");
    }

    private boolean damageTickCheck() {
        return mc.world
                .getPlayers()
                .parallelStream()
                .filter(e -> e != mc.player)
                .filter(e -> e.squaredDistanceTo(mc.player) < 36.0)
                .filter(e -> e.getLastAttacker() == null)
                .filter(e -> !e.isOnGround())
                .anyMatch(e -> e.hurtTime >= 2)
            && !(mc.player.getAttacking() instanceof PlayerEntity);
    }

    @EventTarget
    public void onRender3D(EventRender3D event) {
        if (this.render.isEnabled() && mc.player != null && mc.world != null) {
            if (this.isHoldingCrystal()) {
                BlockPos renderTarget = this.targetObsidianPos != null ? this.targetObsidianPos : this.lastObsidianPos;
                if (renderTarget != null) {
                    this.updateRenderPosition(renderTarget);
                    ColorRGBA baseColor = this.color.getColor();
                    int renderColor = baseColor.getRGB();
                    if (this.isFadingOut) {
                        int alpha = (int)((float)baseColor.getAlpha() * this.fadeOutFactor);
                        renderColor = alpha << 24 | renderColor & 16777215;
                    }

                    Box animatedBox = new Box(this.renderX, this.renderY, this.renderZ, this.renderX + 1.0, this.renderY + 1.0, this.renderZ + 1.0);
                    if (this.glow.isEnabled() && this.isFadingOut) {
                        float glowWidth = this.glowIntensity.getCurrent();

                        for (int i = 0; i < 3; i++) {
                            float layerWidth = glowWidth + (float)i * 0.5F;
                            int alpha = (int)((float)baseColor.getAlpha() * this.fadeOutFactor * 0.3F / (float)(i + 1));
                            int glowColor = alpha << 24 | renderColor & 16777215;
                            Box glowBox = animatedBox.expand((double)i * 0.1);
                            Render3DUtil.drawBox(glowBox, glowColor, layerWidth, true, false, false);
                        }
                    }

                    Render3DUtil.drawBox(animatedBox, renderColor, 2.0F);
                }
            }
        }
    }

    private Color applyFadeOutToColor(Color originalColor) {
        if (!this.isFadingOut) {
            return originalColor;
        } else {
            int alpha = (int)((float)originalColor.getAlpha() * this.fadeOutFactor);
            return new Color(originalColor.getRed(), originalColor.getGreen(), originalColor.getBlue(), alpha);
        }
    }

    private void updateFadeOutFactor() {
        if (this.isFadingOut) {
            long currentTime = System.currentTimeMillis();
            long elapsedTime = currentTime - this.fadeOutStartTime;
            if (elapsedTime >= 2000L) {
                this.fadeOutFactor = 0.0F;
                this.isFadingOut = false;
                this.targetObsidianPos = null;
            } else {
                this.fadeOutFactor = 1.0F - (float)elapsedTime / 2000.0F;
            }
        }
    }

    private void updateRenderPosition(BlockPos targetPos) {
        long currentTime = System.currentTimeMillis();
        if (this.renderX == 0.0 && this.renderY == 0.0 && this.renderZ == 0.0) {
            this.renderX = (double)targetPos.getX();
            this.renderY = (double)targetPos.getY();
            this.renderZ = (double)targetPos.getZ();
            this.lastRenderUpdate = currentTime;
        } else {
            double targetX = (double)targetPos.getX();
            double targetY = (double)targetPos.getY();
            double targetZ = (double)targetPos.getZ();
            long deltaTime = currentTime - this.lastRenderUpdate;
            if (deltaTime > 50L) {
                deltaTime = 50L;
            }

            double deltaX = targetX - this.renderX;
            double deltaY = targetY - this.renderY;
            double deltaZ = targetZ - this.renderZ;
            double moveSpeed = 0.12 * ((double)deltaTime / 16.0);
            if (Math.abs(deltaX) > 0.01) {
                this.renderX += deltaX * moveSpeed;
            } else {
                this.renderX = targetX;
            }

            if (Math.abs(deltaY) > 0.01) {
                this.renderY += deltaY * moveSpeed;
            } else {
                this.renderY = targetY;
            }

            if (Math.abs(deltaZ) > 0.01) {
                this.renderZ += deltaZ * moveSpeed;
            } else {
                this.renderZ = targetZ;
            }

            this.lastRenderUpdate = currentTime;
        }
    }

    private boolean isHoldingCrystal() {
        if (mc.player == null) {
            return false;
        } else {
            ItemStack mainHand = mc.player.getMainHandStack();
            ItemStack offHand = mc.player.getOffHandStack();
            return mainHand.getItem() == Items.END_CRYSTAL || offHand.getItem() == Items.END_CRYSTAL;
        }
    }

    private boolean isLookingAtObsidian() {
        if (mc.player != null && mc.world != null) {
            HitResult hitResult = mc.crosshairTarget;
            if (hitResult == null) {
                return false;
            } else if (hitResult.getType() == Type.BLOCK) {
                BlockHitResult blockHit = (BlockHitResult)hitResult;
                BlockPos blockPos = blockHit.getBlockPos();
                return mc.world.getBlockState(blockPos).getBlock() == Blocks.OBSIDIAN;
            } else {
                if (hitResult.getType() == Type.ENTITY) {
                    EntityHitResult entityHit = (EntityHitResult)hitResult;
                    if (entityHit.getEntity() instanceof EndCrystalEntity crystal) {
                        BlockPos blockPos = new BlockPos(
                            (int)Math.floor(crystal.getX()), (int)Math.floor(crystal.getY()) - 1, (int)Math.floor(crystal.getZ())
                        );
                        return mc.world.getBlockState(blockPos).getBlock() == Blocks.OBSIDIAN;
                    }
                }

                return false;
            }
        } else {
            return false;
        }
    }
}
