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

import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.enemies.GroupFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.towers.Tower;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.util.CommonEventHandles;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import net.minestom.server.MinecraftServer;
import net.minestom.server.collision.Aerodynamics;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.*;
import net.minestom.server.entity.attribute.Attribute;
import net.minestom.server.entity.damage.DamageType;
import net.minestom.server.event.entity.EntityDeathEvent;
import net.minestom.server.event.entity.projectile.ProjectileCollideWithBlockEvent;
import net.minestom.server.event.entity.projectile.ProjectileCollideWithEntityEvent;
import net.minestom.server.event.inventory.InventoryPreClickEvent;
import net.minestom.server.event.item.ItemDropEvent;
import net.minestom.server.event.item.PlayerBeginItemUseEvent;
import net.minestom.server.event.player.*;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemAnimation;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import org.jetbrains.annotations.Nullable;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class TowerdefenseRoom extends InstanceContainer {
    private final static int reach = 30;
    private long lastPlayerAttack = 0;
    private final Player player;
    private final Towerdefense game;
    private final List<EntityCreature> enemies = new ArrayList<>();
    private final List<Tower> towers = new ArrayList<>();
    private final Cursor cursor;
    private int money = 0;

    public TowerdefenseRoom(Player player, Towerdefense game) {
        super(UUID.randomUUID(), Dimension.OVERWORLD.key);
        MinecraftServer.getInstanceManager().registerInstance(this);
        this.player = player;
        this.game = game;
        this.player.setGameMode(GameMode.ADVENTURE);
        this.player.setAllowFlying(true);
        this.addMoney(0);
        this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(reach);
        this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(reach);

        this.player.getInventory().addItemStack(
            ItemStack.of(Material.BOW).withCustomName(Component.text("Schießen", TextColor.color(255, 180, 0)))
        );
        this.player.getInventory().addItemStack(
            ItemStack.of(Material.BARRIER).withCustomName(Component.text("Löschen", TextColor.color(255,0,0)))
        );
        this.game.getAvailableTowers().forEach((material, tower) -> {
            int price = this.game.getPrices().get(tower);
            this.player.getInventory().addItemStack(ItemStack.of(material).withMaxStackSize(price).withAmount(price));
        });

        this.setGenerator(new MazeGenerator());
        BatchUtil.loadAndApplyBatch(this.game.getMazeBatch(), this, () -> {});

        this.cursor = new Cursor(reach);
        this.cursor.setInstance(this);
        this.cursor.setBoundingBox(0,0,0);

        this.eventNode()
            .addListener(EntityDeathEvent.class, event -> {
                if(!(event.getEntity() instanceof EntityCreature enemy)) return;
                this.enemies.remove(enemy);
            })
            .addListener(PlayerTickEvent.class, event -> this.cursor.updateCursorPosition(this.player))
            .addListener(PlayerUseItemEvent.class, event -> this.useItem())
            .addListener(PlayerUseItemOnBlockEvent.class, event -> this.useItem())
            .addListener(PlayerEntityInteractEvent.class, event -> this.useItem())
            .addListener(PlayerHandAnimationEvent.class, event -> this.useItem())
            .addListener(ItemDropEvent.class, CommonEventHandles::cancel)
            .addListener(InventoryPreClickEvent.class, CommonEventHandles::cancel)
            .addListener(PlayerSwapItemEvent.class, CommonEventHandles::cancel)
            .addListener(PlayerBeginItemUseEvent.class, event -> {
                if(event.getAnimation().equals(ItemAnimation.BOW)) event.setCancelled(true);
            });
    }

    private void useItem() {
        Material itemInHand = this.player.getItemInMainHand().material();
        if(itemInHand.equals(Material.BOW)) {
            this.playerAttack();
            return;
        }
        if(itemInHand.equals(Material.BARRIER)) {
            this.removeTower();
            return;
        }
        this.placeTower();
    }

    private void playerAttack() {
        if(this.lastPlayerAttack > System.currentTimeMillis()-200) return;
        Player p = this.player;
        this.lastPlayerAttack = System.currentTimeMillis();
        EntityProjectile projectile = new EntityProjectile(p, EntityType.SPECTRAL_ARROW);
        projectile.setView(p.getPosition().yaw(), p.getPosition().pitch());
        projectile.setNoGravity(true);
        projectile.setAerodynamics(new Aerodynamics(0, 1, 1));

        Vec projectileVelocity = p.getPosition().direction().normalize().mul(20);
        projectile.setVelocity(projectileVelocity);
        projectile.scheduleRemove(Duration.ofSeconds(5));
        projectile.setInstance(this, p.getPosition().add(0, p.getEyeHeight()-0.5, 0));
        projectile.eventNode()
            .addListener(ProjectileCollideWithEntityEvent.class, hitEvent -> {
                if(!(hitEvent.getTarget() instanceof EntityCreature target)) return;
                if(!this.getEnemies().stream().filter(entityCreature -> !entityCreature.isDead()).toList().contains(target)) return;
                target.damage(DamageType.PLAYER_ATTACK, 0.1f);
                if(target.isDead()) this.addMoney((int) target.getAttribute(Attribute.MAX_HEALTH).getBaseValue());
                projectile.remove();
            })
            .addListener(ProjectileCollideWithBlockEvent.class, collisionEvent -> projectile.remove());
    }

    public void addMoney(int amount) {
        this.money += amount;
        this.player.setLevel(this.money);
    }

    public Material getPlayerHandMaterial() {
        return this.player.getItemInMainHand().material();
    }

    private synchronized void placeTower() {
        if(!this.canPlaceActiveTower()) {
            if(this.cursor.isCursorActive() && !this.enoughMoneyForActiveTower()) {
                this.player.sendActionBar(Component.text("Nicht genug Geld!", TextColor.color(255,0,0)));
            }
            return;
        }
        try {
            Material usedMaterial = this.player.getItemInMainHand().material();
            Tower tower = this.game.getAvailableTowers().entrySet().stream()
                .filter(materialClassEntry -> materialClassEntry.getKey().equals(usedMaterial))
                .findFirst()
                .orElseThrow()
                .getValue()
                .getConstructor()
                .newInstance();
            this.setBlock(this.cursor.getTargetBlockPosition(), Block.BLUE_WOOL);
            tower.setInstance(this, this.cursor.getPosition());
            this.towers.add(tower);
            tower.startShooting();
            this.addMoney(-this.game.getPrices().get(tower.getClass()));
        } catch (Exception ignored) {
        }
        this.cursor.updateCursorPosition(this.player);
    }

    private void removeTower() {
        Entity entity = this.player.getLineOfSightEntity(reach, entity1 -> true);
        if(!(entity instanceof Tower tower)) return;
        if(!this.towers.contains(tower)) return;

        this.setBlock(tower.getPosition().sub(0, 0.5, 0), Block.BLACK_WOOL);
        this.addMoney(tower.getSellingPrice(this.game.getPrices().get(tower.getClass())));
        this.towers.remove(tower);
        tower.remove();
    }

    private boolean canPlaceActiveTower() {
        Material usedMaterial = this.player.getItemInMainHand().material();
        return this.canPlaceTower(this.game.getAvailableTowers().get(usedMaterial));
    }

    private boolean canPlaceTower(@Nullable Class<? extends Tower> towerClass) {
        if(towerClass == null) return false;
        return this.cursor.isCursorActive() && this.enoughMoney(towerClass);
    }

    private boolean enoughMoneyForActiveTower() {
        Material usedMaterial = this.player.getItemInMainHand().material();
        return this.enoughMoney(this.game.getAvailableTowers().get(usedMaterial));
    }

    private boolean enoughMoney(@Nullable Class<? extends Tower> towerClass) {
        if(towerClass == null) return true;
        return this.money >= this.game.getPrices().get(towerClass);
    }

    void startWave(List<GroupFactory> enemyGroups) {
        enemyGroups.forEach(groupFactory -> groupFactory.summonGroup(this));
    }

    public void addEnemy(EntityCreature enemy) {
        enemy.setInstance(this, this.game.getMazePath().getFirst());
        this.enemies.add(enemy);
        this.changeEnemyGoal(enemy, 0);
    }

    private synchronized void changeEnemyGoal(EntityCreature enemy, int positionIndex) {
        if(positionIndex == this.game.getMazePath().size()-1) {
            this.enemies.remove(enemy);
            enemy.remove();
            float damage = (float) Math.ceil(enemy.getHealth()/10);
            if(this.player.getHealth() - damage <= 0) {
                this.getEnemies().forEach(Entity::remove);
                this.towers.forEach(Entity::remove);
                this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(this.player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).attribute().defaultValue());
                this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(this.player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).attribute().defaultValue());
                this.player.setInstance(this.game).thenRun(() -> MinecraftServer.getInstanceManager().unregisterInstance(this));
                this.player.heal();
                this.game.getScore().insertResult(this.player);
                return;
            }
            this.player.damage(DamageType.PLAYER_ATTACK, damage);
            return;
        }
        enemy.getNavigator().setPathTo(this.game.getMazePath().get(positionIndex+1), 0.6, () -> this.changeEnemyGoal(enemy, positionIndex+1));
    }

    public Towerdefense getGame() {
        return this.game;
    }

    public List<EntityCreature> getEnemies() {
        return this.enemies;
    }

    public Player getPlayer() {
        return this.player;
    }
}
