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

import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import io.github.togar2.pvp.events.FinalAttackEvent;
import io.github.togar2.pvp.feature.CombatFeatures;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerBlockPlaceEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

public class BlockBattle extends StatelessGame {
    private final Team teamBlue = new Team(new Pos(0,101,20).add(0.5).withView(180, 0), Team.Color.BLUE);
    private final Team teamRed = new Team(new Pos(0, 101, -20).add(0.5), Team.Color.RED);
    private final int itemCount;

    private final Map<Player, Team> teams = new WeakHashMap<>();

    public BlockBattle(int itemCount) {
        super(Dimension.THE_END.key, "Block Battle", new FirstWinsScore());

        this.itemCount = itemCount;

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

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

    @Override
    protected void onLoad(@NotNull CompletableFuture<Void> callback) {
        this.generatePlatform(new Pos(0, 100, 0), Block.GOLD_BLOCK, Block.YELLOW_CONCRETE_POWDER);
        this.generatePlatform(new Pos(0, 101, 0), Block.AIR, Block.SANDSTONE_SLAB);
        this.generatePlatform(new Pos(0, 100, 20), Block.BLUE_CONCRETE, Block.BLUE_CONCRETE_POWDER);
        this.generatePlatform(new Pos(0, 100, -20), Block.RED_CONCRETE, Block.RED_CONCRETE_POWDER);
        this.generatePlatform(new Pos(-5, 101, -14), Block.RED_STAINED_GLASS, Block.AIR);
        this.generatePlatform(new Pos(5, 101, 14), Block.BLUE_STAINED_GLASS, Block.AIR);

        AbsoluteBlockBatch batch = new AbsoluteBlockBatch();

        Pos[] positionsRedGlass = {
            new Pos(2, 102, -9),
            new Pos(-1, 103, -9),
            new Pos(-2, 104, -6),
            new Pos(-5, 103, -7),
            new Pos(-7, 102, -10),
            new Pos(3, 102, -12),
            new Pos(5, 101, -15)
        };

        Pos[] positionsBlueGlass = {
            new Pos(-5, 101, 15),
            new Pos(-3, 102, 12),
            new Pos(-2, 102, 9),
            new Pos(1, 103, 9),
            new Pos(2, 104, 6),
            new Pos(5, 103, 7),
            new Pos(7, 102, 10)
        };

        for(Pos pos : positionsRedGlass)
            batch.setBlock(pos, Block.RED_STAINED_GLASS);

        for(Pos pos : positionsBlueGlass)
            batch.setBlock(pos, Block.BLUE_STAINED_GLASS);

        BatchUtil.loadAndApplyBatch(batch, this, () -> {});
    }

    @Override
    protected void onBlockPlace(@NotNull PlayerBlockPlaceEvent playerBlockPlaceEvent) {
        Pos posGoldBlock = new Pos(playerBlockPlaceEvent.getBlockPosition()).sub(0, 1, 0);
        Block goldBlock = playerBlockPlaceEvent.getInstance().getBlock(posGoldBlock);

        playerBlockPlaceEvent.setCancelled(goldBlock != Block.GOLD_BLOCK);

        playerBlockPlaceEvent.getInstance().scheduler().scheduleNextTick(() -> {
            Pos middle = new Pos(0, 101, 0);
            boolean validBlue = true;
            boolean validRed = true;

            for(int x = middle.blockX()-1; x < middle.blockX()+2; x++) {
                for(int z = middle.blockZ()-1; z < middle.blockZ()+2; z++) {
                    if(playerBlockPlaceEvent.getInstance().getBlock(x, 101, z) != Block.BLUE_WOOL) {
                        validBlue = false;
                        break;
                    }
                }
                if(!validBlue)
                    break;
            }

            for(int x = middle.blockX()-1; x < middle.blockX()+2; x++) {
                for(int z = middle.blockZ()-1; z < middle.blockZ()+2; z++) {
                    if(playerBlockPlaceEvent.getInstance().getBlock(x, 101, z) != Block.RED_WOOL) {
                        validRed = false;
                        break;
                    }
                }
                if(!validRed)
                    break;
            }

            if(!validBlue && !validRed) return;
            var winningTeam = validBlue ? Team.Color.BLUE : Team.Color.RED;
            var winningPlayers = this.teams.entrySet().stream()
                .filter(entry -> entry.getValue().color().equals(winningTeam))
                .map(Map.Entry::getKey)
                .collect(Collectors.toSet());

            this.getScore().insertMultiple(winningPlayers);
            super.stop();
        });
    }

    @Override
    protected void onBlockBreak(@NotNull PlayerBlockBreakEvent playerBlockBreakEvent) {
        boolean isAllowed = Arrays.stream(Team.Color.values())
            .map(Team.Color::getMaterial)
            .map(Material::block)
            .toList()
            .contains(playerBlockBreakEvent.getBlock());
        if(!isAllowed) playerBlockBreakEvent.setCancelled(true);
    }

    @Override
    protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
        if(playerMoveEvent.getNewPosition().y() < 95) {
            var player = playerMoveEvent.getPlayer();

            player.teleport(
                this.isBeforeBeginning
                    ? this.getSpawn()
                    : this.teams.get(player).spawnPosition()
            );

            this.giveItems(player);
        }
    }

    @Override
    protected void onStart() {
        this.setTeams();
        this.getPlayers().forEach(player -> player.teleport(this.teams.get(player).spawnPosition()).thenRun(() -> {
            this.giveItems(player);
            player.setGameMode(GameMode.SURVIVAL);
        }));
    }

    private void generatePlatform(Pos center, Block inner, Block outer) {
        for(int x = center.blockX()-2; x < center.blockX()+3; x++) {
            for(int z = center.blockZ()-2; z < center.blockZ()+3; z++) {
                this.setBlock(x, center.blockY(), z, outer);
            }
        }

        for(int x = center.blockX()-1; x < center.blockX()+2; x++) {
            for(int z = center.blockZ()-1; z < center.blockZ()+2; z++) {
                this.setBlock(x, center.blockY(), z, inner);
            }
        }
    }

    private void setTeams() {
        List<Player> players = new ArrayList<>(this.getPlayers());
        Collections.shuffle(players);
        int halfPlayers = players.size()/2;
        players.subList(0, halfPlayers).forEach(player -> this.teams.put(player, this.teamBlue));
        players.subList(halfPlayers, players.size()).forEach(player -> this.teams.put(player, this.teamRed));
    }

    @Override
    public Pos getSpawn() {
        return new Pos(0, 101, 0).add(0.5);
    }

    private void giveItems(Player player) {
        player.getInventory().clear();
        ItemStack item = ItemStack.of(
            this.teams.get(player).color().getMaterial(),
            this.itemCount
        );
        player.getInventory().addItemStack(item);
    }
}
