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

import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.message.Countdown;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import eu.mhsl.minenet.minigames.message.type.TitleMessage;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
import eu.mhsl.minenet.minigames.util.*;
import eu.mhsl.minenet.minigames.world.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.featureEnriched.ValeGenerator;
import eu.mhsl.minenet.minigames.world.generator.terrain.PlaneTerrainGenerator;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.EquipmentSlot;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.event.player.PlayerStartFlyingWithElytraEvent;
import net.minestom.server.event.player.PlayerStopFlyingWithElytraEvent;
import net.minestom.server.event.player.PlayerUseItemEvent;
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 net.minestom.server.network.packet.server.play.ParticlePacket;
import net.minestom.server.particle.Particle;
import net.minestom.server.sound.SoundEvent;
import org.jetbrains.annotations.NotNull;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CompletableFuture;


public class ElytraRace extends StatelessGame {
    private final ValeGenerator vale = new ValeGenerator();

    private final int gameHeight = 0;
    private final int seaLevel = -55;
    private final int ringSpacing = 100;
    private final int ringCount;

    private final Material boostMaterial = Material.FIREWORK_ROCKET;
    private final Material resetMaterial = Material.RED_DYE;
    private final int boostMultiplier = 50;
    private final Material ringMaterial = Material.GOLD_BLOCK;
    private final Map<Player, CheckPointData> playerCheckpoints = new HashMap<>();
    private int generatedUntil = 0;

    public ElytraRace(int ringCount) {
        super(Dimension.OVERWORLD.key, "ElytraRace", new FirstWinsScore());

        this.ringCount = ringCount;

        this.setGenerator(this.vale);
        this.vale.setCalculateSeaLevel(point -> this.seaLevel);
        this.vale.setXShiftMultiplier(integer -> NumberUtil.map(integer, 50, 500, 0, 1));
        this.vale.addMixIn(new PlaneTerrainGenerator(this.gameHeight, Block.BARRIER));

        this.eventNode().addListener(PlayerUseItemEvent.class, playerUseItemEvent -> {
            Player player = playerUseItemEvent.getPlayer();
            Material usedMaterial = playerUseItemEvent.getItemStack().material();

            if(usedMaterial.equals(this.boostMaterial)) {
                if(!player.isFlyingWithElytra()) return;

                this.boost(player);
                InventoryUtil.removeItemFromPlayer(player, this.boostMaterial, 1);
            } else if(usedMaterial.equals(this.resetMaterial)) {
                this.toCheckpoint(player);
                InventoryUtil.removeItemFromPlayer(player, this.resetMaterial, 1);
            }
        });

        this.eventNode().addListener(PlayerStopFlyingWithElytraEvent.class, playerStopFlyingWithElytraEvent -> {
            Player player = playerStopFlyingWithElytraEvent.getPlayer();
            if(Position.blocksBelowPlayer(this, player).contains(this.ringMaterial.block())) {
                player.setFlyingWithElytra(true);
                this.boost(player);
            } else {
                this.toCheckpoint(player);
//                getScore().insertResult(playerStopFlyingWithElytraEvent.getPlayer());
//                playerStopFlyingWithElytraEvent.getPlayer().setGameMode(GameMode.SPECTATOR);
            }
        });

        this.eventNode().addListener(PlayerStartFlyingWithElytraEvent.class, playerStartFlyingWithElytraEvent -> {
            if(!this.isRunning) {
                playerStartFlyingWithElytraEvent.getPlayer().setFlyingWithElytra(false);
                return;
            }

            this.boost(playerStartFlyingWithElytraEvent.getPlayer());
        });
    }

    @Override
    protected void onLoad(@NotNull CompletableFuture<Void> callback) {
        Point spawnpoint = new Pos(this.vale.getXShiftAtZ(0), -46, 0);
        GeneratorUtils.iterateArea(spawnpoint.sub(5, 0, 5), spawnpoint.add(5, 0, 5), point -> this.setBlock(point, BlockPallet.STREET.rnd()));

        this.generateRing(this.ringSpacing);
        this.generateRing(this.ringSpacing * 2);
        callback.complete(null);
    }

    @Override
    protected void onStart() {
        this.getPlayers().forEach(player -> {
            player.getInventory().setEquipment(EquipmentSlot.CHESTPLATE, (byte) 0, ItemStack.of(Material.ELYTRA));
            for(int i = 0; i < 3; i++) {
                player.getInventory().setItemStack(i, ItemStack.builder(this.boostMaterial).customName(TranslatedComponent.byId("boost").getAssembled(player)).build());
            }
            this.addResetItemToPlayer(player);
        });
    }

    @Override
    protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
        Player player = playerMoveEvent.getPlayer();
        Point newPos = playerMoveEvent.getNewPosition();

        if(this.isBeforeBeginning && playerMoveEvent.getNewPosition().y() < this.getSpawn().y()) {
            player.teleport(this.getSpawn());
            return;
        }

        this.playerCheckpoints.putIfAbsent(player, new CheckPointData(0, this.ringSpacing));

        if(newPos.z() > this.generatedUntil - this.ringSpacing) {
            this.generateRing(this.generatedUntil + this.ringSpacing);
        }

        if(newPos.z() > this.playerCheckpoints.get(player).nextCheckpoint) {
            this.playerCheckpoints.put(player, this.playerCheckpoints.get(player).next(this.ringSpacing));
            this.boost(player);
        }

        if(newPos.y() > this.gameHeight - 5) {
            Point particlePoint = newPos.withY(this.gameHeight);
            ParticlePacket particle = new ParticlePacket(
                Particle.WAX_ON,
                particlePoint.blockX(),
                particlePoint.blockY(),
                particlePoint.withZ(z -> z + 10).blockZ(),
                20,
                0,
                30,
                1f,
                Math.toIntExact((long) NumberUtil.map(newPos.y(), this.gameHeight - 5, this.gameHeight, 50, 500))
            );
            player.sendPacket(particle);
        }

        if(this.getBlock(player.getPosition()).equals(Block.WATER)) {
            this.toCheckpoint(player);
        }

        if(newPos.z() > this.ringCount * this.ringSpacing) {
            this.getScore().insertResult(player);
            player.setGameMode(GameMode.SPECTATOR);
            player.setFlyingWithElytra(false);
        }
    }

    @Override
    public Pos getSpawn() {
        return new Pos(this.vale.getXShiftAtZ(0), -45, 0);
    }

    private void addResetItemToPlayer(Player p) {
        p.getInventory().setItemStack(8, ItemStack.builder(this.resetMaterial).customName(TranslatedComponent.byId("reset").getAssembled(p)).build());
    }

    private Point getRingPositionAtZ(int z) {
        Random random = new Random(this.hashCode() + z);
        return new Pos(this.vale.getXShiftAtZ(z), -45 + random.nextInt(-5, 15), z);
    }

    private void generateRing(int zPos) {
        if(zPos > this.ringCount * this.ringSpacing) return;
        boolean isLast = (zPos == this.ringCount * this.ringSpacing);

        this.generatedUntil = zPos;
        AbsoluteBlockBatch batch = new AbsoluteBlockBatch();

        Point ringPos = this.getRingPositionAtZ(zPos);
        GeneratorUtils.iterateArea(
            ringPos.sub(100, 0, 0).withY(0),
            ringPos.add(100, 0, 0).withY(this.seaLevel),
            point -> batch.setBlock(point, Block.BARRIER)
        );
        GeneratorUtils.iterateArea(
            ringPos.sub(3, 3, 0),
            ringPos.add(3, 3, 0),
            point -> batch.setBlock(point, isLast ? Block.DIAMOND_BLOCK : this.ringMaterial.block())
        );
        GeneratorUtils.iterateArea(
            ringPos.sub(2, 2, 0),
            ringPos.add(2, 2, 0),
            point -> batch.setBlock(point, Block.AIR)
        );

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

    private void boost(Player player) {
        player.playSound(Sound.sound(SoundEvent.ENTITY_FIREWORK_ROCKET_LAUNCH, Sound.Source.VOICE, 1f, 1f));

        Vec playerVelocity = player.getPosition().direction();
        player.setVelocity(
            player.getVelocity().add(playerVelocity.mul(this.boostMultiplier))
                .withY(playerVelocity.withY(v -> v * this.boostMultiplier).y())
        );
    }

    private void toCheckpoint(Player p) {
        CheckPointData data = this.playerCheckpoints.get(p);
        Point checkpointPos = this.getRingPositionAtZ(this.playerCheckpoints.get(p).currentCheckpoint);
        if(data.currentCheckpoint == 0) checkpointPos = this.getSpawn().add(0, 3, 0);
        p.setVelocity(Vec.ZERO);
        p.setFlyingWithElytra(false);
        p.teleport(Pos.fromPoint(checkpointPos).add(0.5, 0, 0.5));

        p.setFlying(true);
        p.setFlyingSpeed(0);

        new Countdown()
            .countdown(
                Audience.audience(p),
                3,
                countdownModifier ->
                    countdownModifier.message = new TitleMessage(
                        Duration.ofMillis(300),
                        Duration.ofMillis(700)
                    )
                        .subtitle(
                            subtitleMessage ->
                                subtitleMessage
                                    .appendTranslated("game_ElytraRace#launchIn")
                                    .appendSpace()
                                    .appendStatic(Component.text(countdownModifier.timeLeft, NamedTextColor.GREEN))
                                    .appendSpace()
                                    .appendTranslated(countdownModifier.timeLeft == 1 ? "common#second" : "common#seconds")
                        )
            ).thenRun(() -> {
                p.setFlying(false);
                p.setFlyingSpeed(1);
                p.setFlyingWithElytra(true);
                this.boost(p);
                this.addResetItemToPlayer(p);
            });
    }

    private record CheckPointData(int currentCheckpoint, int nextCheckpoint) {
        public CheckPointData next(int spacing) {
            return new CheckPointData(this.nextCheckpoint, this.nextCheckpoint + spacing);
        }
    }
}
