package net.minestom.server.inventory.click.integration;

import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.inventory.InventoryPreClickEvent;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.inventory.click.ClickType;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.network.packet.client.play.ClientClickWindowPacket;
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
import net.minestom.testing.Env;
import net.minestom.testing.EnvTest;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

@EnvTest
public class RightClickIntegrationTest {

    @Test
    public void rightSelf(Env env) {
        var instance = env.createFlatInstance();
        var player = env.createPlayer(instance, new Pos(0, 40, 0));
        var inventory = player.getInventory();
        var listener = env.listen(InventoryPreClickEvent.class);
        inventory.setItemStack(1, ItemStack.of(Material.DIAMOND));
        inventory.setItemStack(2, ItemStack.of(Material.DIAMOND));
        // Empty click
        {
            listener.followup(event -> {
                assertNull(event.getInventory()); // Player inventory
                assertEquals(0, event.getSlot());
                assertEquals(ClickType.RIGHT_CLICK, event.getClickType());
                assertEquals(ItemStack.AIR, inventory.getCursorItem());
            });
            rightClick(player, 0);
        }
        // Pickup diamond
        {
            listener.followup(event -> {
                assertEquals(1, event.getSlot());
                assertEquals(ItemStack.AIR, inventory.getCursorItem());
                assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
            });
            rightClick(player, 1);
            assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem());
            assertEquals(ItemStack.AIR, inventory.getItemStack(1));
        }
        // Place it back
        {
            listener.followup(event -> {
                assertEquals(1, event.getSlot());
                assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem());
                assertEquals(ItemStack.AIR, inventory.getItemStack(1));
            });
            rightClick(player, 1);
            assertEquals(ItemStack.AIR, inventory.getCursorItem());
            assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
        }
        // Pickup diamond
        {
            listener.followup(event -> {
                assertEquals(1, event.getSlot());
                assertEquals(ItemStack.AIR, inventory.getCursorItem());
                assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
            });
            rightClick(player, 1);
            assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem());
            assertEquals(ItemStack.AIR, inventory.getItemStack(1));
        }
        // Stack diamond
        {
            listener.followup(event -> {
                assertEquals(2, event.getSlot());
                assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem());
                assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(2));
            });
            rightClick(player, 2);
            assertEquals(ItemStack.AIR, inventory.getCursorItem());
            assertEquals(ItemStack.of(Material.DIAMOND, 2), inventory.getItemStack(2));
        }
        // Cancel event
        {
            listener.followup(event -> event.setCancelled(true));
            rightClick(player, 2);
            assertEquals(ItemStack.AIR, inventory.getCursorItem(), "Left click cancellation did not work");
            assertEquals(ItemStack.of(Material.DIAMOND, 2), inventory.getItemStack(2));
        }
        // Change items
        {
            listener.followup(event -> {
                event.setClickedItem(ItemStack.of(Material.DIAMOND, 5));
                event.setCursorItem(ItemStack.of(Material.DIAMOND));
            });
            rightClick(player, 1);
            assertEquals(ItemStack.AIR, inventory.getCursorItem());
            assertEquals(ItemStack.of(Material.DIAMOND, 6), inventory.getItemStack(1));
        }
    }

    @Test
    public void rightExternal(Env env) {
        var instance = env.createFlatInstance();
        var player = env.createPlayer(instance, new Pos(0, 40, 0));
        var inventory = new Inventory(InventoryType.HOPPER, "test");
        player.openInventory(inventory);
        var listener = env.listen(InventoryPreClickEvent.class);
        inventory.setItemStack(1, ItemStack.of(Material.DIAMOND));
        // Empty click in player inv
        {
            listener.followup(event -> {
                assertNull(event.getInventory()); // Player inventory
                assertEquals(0, event.getSlot());
                assertEquals(ClickType.RIGHT_CLICK, event.getClickType());
                assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
            });
            rightClick(player, 0);
        }
        // Pickup diamond
        {
            listener.followup(event -> {
                assertEquals(inventory, event.getInventory());
                assertEquals(1, event.getSlot());
                assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
                assertEquals(ItemStack.of(Material.DIAMOND), inventory.getItemStack(1));
            });
            rightClickOpenInventory(player, 1);
            assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem(player));
            assertEquals(ItemStack.AIR, inventory.getItemStack(1));
        }
        // Place back to player inv
        {
            listener.followup(event -> {
                assertNull(event.getInventory());
                assertEquals(1, event.getSlot());
                assertEquals(ItemStack.of(Material.DIAMOND), inventory.getCursorItem(player));
                assertEquals(ItemStack.AIR, inventory.getItemStack(1));
                assertEquals(ItemStack.AIR, player.getInventory().getItemStack(1));
            });
            rightClick(player, 1);
            assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
            assertEquals(ItemStack.of(Material.DIAMOND), player.getInventory().getItemStack(1));
        }
        // Cancel event
        {
            listener.followup(event -> event.setCancelled(true));
            rightClick(player, 1);
            assertEquals(ItemStack.of(Material.DIAMOND), player.getInventory().getItemStack(1), "Left click cancellation did not work");
            assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
        }
        // Change items
        {
            listener.followup(event -> {
                assertNull(event.getInventory());
                assertEquals(9, event.getSlot());
                event.setClickedItem(ItemStack.of(Material.DIAMOND, 5));
                event.setCursorItem(ItemStack.of(Material.DIAMOND));
            });
            rightClick(player, 9);
            assertEquals(ItemStack.AIR, inventory.getCursorItem(player));
            assertEquals(ItemStack.of(Material.DIAMOND, 6), player.getInventory().getItemStack(9));
        }
    }

    private void rightClickOpenInventory(Player player, int slot) {
        _rightClick(player.getOpenInventory(), true, player, slot);
    }

    private void rightClick(Player player, int slot) {
        _rightClick(player.getOpenInventory(), false, player, slot);
    }

    private void _rightClick(Inventory openInventory, boolean clickOpenInventory, Player player, int slot) {
        final byte windowId = openInventory != null ? openInventory.getWindowId() : 0;
        if (clickOpenInventory) {
            assert openInventory != null;
            // Do not touch slot
        } else {
            int offset = openInventory != null ? openInventory.getInnerSize() : 0;
            slot = PlayerInventoryUtils.convertToPacketSlot(slot);
            if (openInventory != null) {
                slot = slot - 9 + offset;
            }
        }
        player.addPacketToQueue(new ClientClickWindowPacket(windowId, 0, (short) slot, (byte) 1,
                ClientClickWindowPacket.ClickType.PICKUP, List.of(), ItemStack.AIR));
        player.interpretPacketQueue();
    }
}
