package eu.mhsl.minenet.minigames.instance.game.stateless.types.spaceSnake;

import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.PointsWinScore;
import eu.mhsl.minenet.minigames.util.MaterialUtil;
import io.github.togar2.pvp.events.FinalAttackEvent;
import io.github.togar2.pvp.events.PrepareAttackEvent;
import io.github.togar2.pvp.feature.CombatFeatures;
import net.kyori.adventure.sound.Sound;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.metadata.other.FallingBlockMeta;
import net.minestom.server.event.entity.EntityTickEvent;
import net.minestom.server.event.player.PlayerBlockPlaceEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.WorldBorder;
import net.minestom.server.instance.block.Block;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.sound.SoundEvent;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class SpaceSnake extends StatelessGame {
    private final Map<Player, PlayState> playerStates = new WeakHashMap<>();
    private final Supplier<Integer> posInBoundsH = () -> this.rnd.nextInt(-60, 300);
    private int mapSize;
    private final Supplier<Integer> posInBoundsW = () -> this.rnd.nextInt(-this.mapSize / 2, this.mapSize / 2);

    public SpaceSnake(int mapSize, int powerUpCount) {
        super(Dimension.THE_END.key, "spaceSnake", new PointsWinScore());
        this.mapSize = mapSize;
        this.setWorldBorder(new WorldBorder(this.mapSize, 0, 0, 0, 0));

        for(int i = 0; i < powerUpCount; i++) {
            this.spawnPowerUp();
        }

        this.eventNode().addChild(
            CombatFeatures.empty()
                .add(CombatFeatures.VANILLA_ATTACK)
                .add(CombatFeatures.VANILLA_DAMAGE)
                .add(CombatFeatures.VANILLA_KNOCKBACK)
                .build()
                .createNode()
        );

        this.eventNode().addListener(PrepareAttackEvent.class, prepareAttackEvent -> {
            if(this.isBeforeBeginning) prepareAttackEvent.setCancelled(true);
        });

        this.eventNode().addListener(FinalAttackEvent.class, finalAttackEvent -> {
            finalAttackEvent.setBaseDamage(0);
            ((Player) finalAttackEvent.getTarget()).setHealth(20);
        });
    }

    @Override
    protected void onStart() {
        this.getPlayers().forEach(player -> {
            player.setGameMode(GameMode.SURVIVAL);
            this.updateInv(player);
            player.setHeldItemSlot((byte) 1);
        });
    }

    @Override
    protected void onStop() {
        this.getPlayers().forEach(player -> this.getScore().insertResult(player, this.playerStates.get(player).length.get()));
    }

    @Override
    protected boolean onPlayerJoin(Player p) {
        Pos spawn = new Pos(this.posInBoundsW.get(), -60, this.posInBoundsW.get());
        PlayState state = new PlayState(
            new AtomicInteger(3),
            new ArrayDeque<>(List.of(spawn)),
            MaterialUtil.getRandomFullBlock(material -> !material.equals(Material.DIAMOND_BLOCK)),
            spawn
        );
        this.playerStates.put(p, state);
        this.setBlock(spawn, state.blockType.block());
        MinecraftServer.getSchedulerManager().scheduleNextTick(
            () -> p.teleport(this.getSaveSpawn(spawn))
        );
        return super.onPlayerJoin(p);
    }

    @Override
    protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
        PlayState state = this.playerStates.get(playerMoveEvent.getPlayer());
        if(this.isBeforeBeginning) {
            boolean falling = state.blocks.stream().anyMatch(pos -> pos.y() > playerMoveEvent.getNewPosition().y());
            if(falling) playerMoveEvent.getPlayer().teleport(this.getSaveSpawn(state.spawn));
            return;
        }

        if(playerMoveEvent.getNewPosition().y() < -64) {
            this.getScore().insertResult(playerMoveEvent.getPlayer(), state.length.get());
            playerMoveEvent.getPlayer().teleport(this.getSpawn());
            playerMoveEvent.getPlayer().setGameMode(GameMode.SPECTATOR);
            long livingPlayers = this.getPlayers().stream()
                .filter(p -> !this.getScore().hasResult(p))
                .count();
            if(livingPlayers == 1) this.setTimeLimit(10);
            if(livingPlayers == 0) this.stop();
        }
    }

    @Override
    protected void onBlockPlace(@NotNull PlayerBlockPlaceEvent playerBlockPlaceEvent) {
        if(this.isBeforeBeginning) {
            playerBlockPlaceEvent.setCancelled(true);
            return;
        }

        PlayState state = this.playerStates.get(playerBlockPlaceEvent.getPlayer());
        state.blocks.add(playerBlockPlaceEvent.getBlockPosition().asVec().asPosition());
        state.cutToLength(pos -> this.setBlock(pos, Block.AIR));

        MinecraftServer.getSchedulerManager().scheduleNextTick(() -> this.updateInv(playerBlockPlaceEvent.getPlayer()));
        playerBlockPlaceEvent.getPlayer().setLevel(state.length.get());
    }

    private Pos getSaveSpawn(Pos blockPos) {
        return blockPos.add(0.5).withY((y) -> y + 2);
    }

    private void updateInv(Player player) {
        PlayerInventory inventory = player.getInventory();
        inventory.clear();
        inventory.addItemStack(ItemStack.of(Material.STICK, 1).with(builder -> builder.glowing(true)));
        inventory.addItemStack(ItemStack.of(this.playerStates.get(player).blockType, 64));
    }

    private void spawnPowerUp() {
        Pos spawnPos = new Pos(this.posInBoundsW.get(), this.posInBoundsH.get(), this.posInBoundsW.get());
        Entity display = new Entity(EntityType.FALLING_BLOCK);
        ((FallingBlockMeta) display.getEntityMeta()).setBlock(Block.DIAMOND_BLOCK);
        display.setGlowing(true);
        display.setNoGravity(true);
        display.setInstance(this, spawnPos);

        display.eventNode().addListener(EntityTickEvent.class, onTick -> {
            Player player = this.getPlayers().stream()
                .filter(p -> !this.getScore().hasResult(p))
                .filter(p -> p.getBoundingBox()
                    .grow(1, 1, 1)
                    .intersectBox(display.getPosition().sub(p.getPosition()), display.getBoundingBox())
                )
                .findAny()
                .orElse(null);
            if(player == null) return;

            this.spawnPowerUp();
            display.remove();
            this.onPowerup(player);
        });
    }

    private void onPowerup(Player player) {
        PlayState state = this.playerStates.get(player);
        state.length.incrementAndGet();
        player.setLevel(player.getLevel() + 1);
        player.playSound(Sound.sound(SoundEvent.ENTITY_EXPERIENCE_ORB_PICKUP, Sound.Source.PLAYER, 1f, 1f));
    }

    record PlayState(AtomicInteger length, Queue<Pos> blocks, Material blockType, Pos spawn) {
        public void cutToLength(Consumer<Pos> removed) {
            while(this.blocks.size() > this.length.get()) {
                removed.accept(this.blocks.poll());
            }
        }
    }
}
