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

import com.darkmagician6.eventapi.EventTarget;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import lombok.Generated;
import net.minecraft.item.ItemStack;
import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket;
import net.minecraft.network.packet.s2c.play.OpenScreenS2CPacket;
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
import net.minecraft.screen.slot.Slot;
import net.minecraft.screen.slot.SlotActionType;
import zenith.zov.Zenith;
import zenith.zov.base.autobuy.item.ItemBuy;
import zenith.zov.base.events.impl.input.EventKey;
import zenith.zov.base.events.impl.player.EventUpdate;
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.ButtonSetting;
import zenith.zov.client.modules.api.setting.impl.ModeSetting;
import zenith.zov.client.screens.autobuy.items.AutoInventoryBuyScreen;
import zenith.zov.client.screens.autobuy.items.AutoInventoryItem;
import zenith.zov.utility.game.other.MessageUtil;
import zenith.zov.utility.game.server.AutoBuyUtil;
import zenith.zov.utility.math.MathUtil;
import zenith.zov.utility.math.Timer;

@ModuleAnnotation(
    name = "AutoSbor",
    category = Category.MISC,
    description = "Automatically purchases inventory"
)
public final class AutoSbor extends Module {
    public static final AutoSbor INSTANCE = new AutoSbor();
    private ArrayList<AutoInventoryItem> zakup = new ArrayList<>();
    private ModeSetting mode = new ModeSetting("Mode");
    private ModeSetting.Value funtimeMode = new ModeSetting.Value(this.mode, "FunTime");
    private ModeSetting.Value hollyworldMode = new ModeSetting.Value(this.mode, "HollyWorld").select();
    private final ButtonSetting executeButton = new ButtonSetting("Open Menu", () -> mc.setScreen(new AutoInventoryBuyScreen(this.getLib(), this.zakup)));
    private int index = 0;
    private List<String> funtimeBypass = new ArrayList<>();
    private final Timer timerForAhCommand = new Timer();
    private final Timer timerForWaitBuy = new Timer();
    private final Timer timerForChangeStadia = new Timer();
    private int stadia = 0;
    private int sumTrati = 0;
    private int buyCount = 0;
    private int lastTrati = 0;
    private int lastCount = 0;
    private int baseInvCount = -1;
    Timer startSendUpdate = new Timer();

    private AutoSbor() {
    }

    @EventTarget
    public void update(EventUpdate event) {
        if (this.index >= this.zakup.size()) {
            this.toggle();
        } else {
            AutoInventoryItem currentItem = this.zakup.get(this.index);
            if (currentItem == null) {
                this.toggle();
            } else {
                if (this.funtimeMode.isSelected()) {
                    if (mc.currentScreen == null) {
                        if (this.timerForAhCommand.finished(500L)) {
                            mc.getNetworkHandler().sendCommand("ah search " + currentItem.getItemBuy().getSearchName());
                            mc.player.setPitch((float)MathUtil.getRandom(-90.0, 90.0));
                            mc.player.setYaw((float)((double)mc.player.getYaw() + MathUtil.getRandom(-90.0, 90.0)));
                            this.timerForAhCommand.reset();
                        }

                        return;
                    }

                    if (!this.startSendUpdate.finished(400L)) {
                        return;
                    }

                    if (AutoBuyUtil.isWaitBuy(mc.player.currentScreenHandler)) {
                        if (this.timerForWaitBuy.finished(400L)) {
                            this.click(10, 0, SlotActionType.PICKUP);
                            this.timerForWaitBuy.reset();
                        }

                        return;
                    }

                    if (this.stadia == 0) {
                        if (this.baseInvCount == -1) {
                            this.baseInvCount = this.getCountInInventory(currentItem);
                        }

                        for (int i = 0; i < 44; i++) {
                            Slot slot = mc.player.currentScreenHandler.getSlot(i);
                            ItemStack stack = slot.getStack();
                            if (currentItem.isBuy(stack)) {
                                this.funtimeBypass.add(AutoBuyUtil.getTagFuntimeNotTempElements(stack));
                            }
                        }

                        this.click(49, 0, SlotActionType.PICKUP);
                        this.stadia = 1;
                        this.timerForChangeStadia.reset();
                        return;
                    }

                    if (this.stadia == 1 && this.timerForChangeStadia.finished(500L)) {
                        int gained = this.getCountInInventory(currentItem) - this.baseInvCount;
                        if (gained < this.buyCount) {
                            this.buyCount = Math.max(0, this.buyCount - this.lastCount);
                            this.sumTrati = Math.max(0, this.sumTrati - this.lastTrati);
                        }

                        Slot goodSlot = null;
                        double bestUnitPrice = Double.MAX_VALUE;
                        int countBuy = currentItem.getCountBuy();

                        for (int ix = 0; ix < 44; ix++) {
                            Slot slot = mc.player.currentScreenHandler.getSlot(ix);
                            ItemStack stack = slot.getStack();
                            if (this.funtimeBypass.contains(AutoBuyUtil.getTagFuntimeNotTempElements(stack))) {
                                int price = AutoBuyUtil.getPrice(stack);
                                int countItem = stack.getCount();
                                if (countItem <= Math.max(1, countBuy) - this.buyCount && (long)price <= currentItem.getMaxSumBuy() - (long)this.sumTrati) {
                                    double unitPrice = (double)price / (double)countItem;
                                    if (unitPrice < bestUnitPrice) {
                                        bestUnitPrice = unitPrice;
                                        goodSlot = slot;
                                    }
                                }
                            }
                        }

                        if (goodSlot != null) {
                            int price = AutoBuyUtil.getPrice(goodSlot.getStack());
                            this.lastTrati = price;
                            this.lastCount = goodSlot.getStack().getCount();
                            this.sumTrati += price;
                            this.buyCount = this.buyCount + this.lastCount;
                            this.timerForChangeStadia.reset();
                            this.click(goodSlot.id, 0, SlotActionType.PICKUP_ALL);
                            MessageUtil.displayMessage(MessageUtil.LogLevel.INFO, "Trying " + currentItem.getItemBuy().getSearchName());
                        } else {
                            int totalGained = Math.max(0, this.getCountInInventory(currentItem) - this.baseInvCount);
                            boolean success = totalGained > 0;
                            MessageUtil.displayMessage(
                                success ? MessageUtil.LogLevel.INFO : MessageUtil.LogLevel.WARN,
                                (success ? "Bought: +" : "Not bought: +") + totalGained + " " + currentItem.getItemBuy().getSearchName()
                            );
                            this.sumTrati = 0;
                            this.stadia = 0;
                            this.buyCount = 0;
                            this.lastCount = 0;
                            this.lastTrati = 0;
                            this.baseInvCount = -1;
                            this.funtimeBypass.clear();
                            mc.player.closeScreen();
                            this.timerForAhCommand.reset();
                            this.index++;
                        }
                    }
                }
            }
        }
    }

    @EventTarget
    public void packet(EventPacket event) {
        if (event.isReceive()
            && (
                event.getPacket() instanceof ScreenHandlerSlotUpdateS2CPacket screenHandlerSlotUpdateS2CPacket
                    || event.getPacket() instanceof OpenScreenS2CPacket
            )) {
            this.startSendUpdate.reset();
        }

        if (event.getPacket() instanceof ClickSlotC2SPacket clickSlotC2SPacket && clickSlotC2SPacket.getSlot() == 50) {
            this.click(48, 0, SlotActionType.PICKUP);
        }
    }

    public int getCountInInventory(AutoInventoryItem item) {
        int count = 0;

        for (ItemStack slot : mc.player.getInventory().main) {
            if (!slot.isEmpty() && item.isBuy(slot)) {
                count += slot.getCount();
            }
        }

        return count;
    }

    @Override
    public void onEnable() {
        if (mc.player == null) {
            this.setEnabled(false);
        } else {
            if (mc.currentScreen != null) {
                mc.player.closeHandledScreen();
            }

            this.timerForAhCommand.reset();
            super.onEnable();
            MessageUtil.displayInfo("TEMPORARILY NOT WORKING");
            this.toggle();
        }
    }

    @Override
    public void onDisable() {
        this.index = 0;
        this.sumTrati = 0;
        this.stadia = 0;
        this.funtimeBypass.clear();
        this.timerForAhCommand.reset();
        this.buyCount = 0;
        super.onDisable();
    }

    @Override
    public JsonObject save() {
        JsonObject object = super.save();
        JsonObject propertiesObject = new JsonObject();

        for (AutoInventoryItem autoInventoryItem : this.zakup) {
            propertiesObject.add(
                autoInventoryItem.getItemBuy().getSearchName()
                    + autoInventoryItem.getItemBuy().getDisplayName()
                    + autoInventoryItem.getItemBuy().getCategory().name(),
                autoInventoryItem.save()
            );
        }

        object.add("AutoInventoryItems", propertiesObject);
        return object;
    }

    @Override
    public void load(JsonObject object) {
        super.load(object);
        this.zakup.clear();
        if (object.has("AutoInventoryItems") && object.get("AutoInventoryItems").isJsonObject()) {
            JsonObject propertiesObject = object.getAsJsonObject("AutoInventoryItems");

            for (Entry<String, JsonElement> entry : propertiesObject.entrySet()) {
                String key = entry.getKey();
                JsonObject itemJson = entry.getValue().getAsJsonObject();
                ItemBuy itemBuy = this.findItemBuyByKey(key);
                if (itemBuy != null) {
                    AutoInventoryItem autoInventoryItem = AutoInventoryBuyScreen.createItem(itemBuy);
                    autoInventoryItem.load(itemJson);
                    this.zakup.add(autoInventoryItem);
                }
            }
        }
    }

    private void click(int slotId, int buttonId, SlotActionType slotActionType) {
        mc.interactionManager.clickSlot(mc.player.currentScreenHandler.syncId, slotId, buttonId, slotActionType, mc.player);
    }

    private ItemBuy findItemBuyByKey(String key) {
        for (ItemBuy item : this.getLib()) {
            String generatedKey = item.getSearchName() + item.getDisplayName() + item.getCategory().name();
            if (generatedKey.equals(key)) {
                return item;
            }
        }

        return null;
    }

    private ArrayList<ItemBuy> getLib() {
        ArrayList<ItemBuy> allItemBuys = new ArrayList<>(
            this.funtimeMode.isSelected() ? Zenith.getInstance().getAutoBuyManager().getFuntime() : Zenith.getInstance().getAutoBuyManager().getHollyworld()
        );
        allItemBuys.addAll(Zenith.getInstance().getAutoBuyManager().getVanilla());
        return allItemBuys;
    }

    @EventTarget
    public void onKey(EventKey event) {
        this.toggle();
    }

    @Generated
    public ArrayList<AutoInventoryItem> getZakup() {
        return this.zakup;
    }
}
