/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.towerrun.listener;

import de.steamwar.inventory.SWItem;
import de.steamwar.linkage.Linked;
import de.steamwar.towerrun.TowerRun;
import de.steamwar.towerrun.config.Config;
import de.steamwar.towerrun.config.WorldConfig;
import de.steamwar.towerrun.game.TowerRunGame;
import de.steamwar.towerrun.state.GameStateBukkitListener;
import de.steamwar.towerrun.state.GameStates;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityRegainHealthEvent;
import org.bukkit.event.entity.ItemSpawnEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Linked
public class IngameListener extends GameStateBukkitListener {

    private int time = 0;
    private final Map<Pos, Integer> blocksToMelt = new HashMap<>();
    private BukkitRunnable blocksToMeltRunnable;
    private BukkitRunnable antiCampRunnable;

    @AllArgsConstructor
    @EqualsAndHashCode
    private class Pos {
        private final int x;
        private final int y;
        private final int z;
    }

    public IngameListener() {
        super(EnumSet.of(GameStates.RUNNING));
    }

    @Override
    public void enable() {
        super.enable();
        blocksToMeltRunnable = new BukkitRunnable() {
            @Override
            public void run() {
                List<Pos> blocksToBreak = blocksToMelt.entrySet().stream()
                        .filter(entry -> entry.getValue() <= time)
                        .map(Map.Entry::getKey)
                        .collect(Collectors.toList());
                time++;
                if (blocksToBreak.isEmpty()) {
                    return;
                }
                for (Pos pos : blocksToBreak) {
                    blocksToMelt.remove(pos);
                    Block block = Bukkit.getWorlds().get(0).getBlockAt(pos.x, pos.y, pos.z);
                    if (block.getType() == Material.AIR || block.getType() == Material.LAVA) continue;
                    block.setType(Material.AIR);
                    block.getWorld().playSound(block.getLocation(), Sound.BLOCK_FIRE_EXTINGUISH, 0.1F, 1);
                }
            }
        };
        blocksToMeltRunnable.runTaskTimer(TowerRun.getInstance(), 0, 1);

        antiCampRunnable = new BukkitRunnable() {
            @Override
            public void run() {
                double minY = TowerRunGame.PLAYERS_ALIVE.stream()
                        .mapToDouble(p -> p.player().getLocation().getY())
                        .average()
                        .orElse(0.0);

                List<Player> toDamage = new ArrayList<>();
                TowerRunGame.PLAYERS_ALIVE.forEach(towerRunPlayer -> {
                    if (towerRunPlayer.player().getLocation().getY() - minY > 30) {
                        towerRunPlayer.player().sendTitle("", TowerRun.getMessage().parse("CATCH_UP_WARNING", towerRunPlayer.player()), 5, 30, 5);
                    }
                    if (towerRunPlayer.player().getLocation().getY() - minY > 50) {
                        toDamage.add(towerRunPlayer.player());
                    }
                });
                toDamage.forEach(p -> p.damage(1.0));
            }
        };
        antiCampRunnable.runTaskTimer(TowerRun.getInstance(), 100, 100);
    }

    @Override
    public void disable() {
        super.disable();
        blocksToMeltRunnable.cancel();
        blocksToMeltRunnable = null;
        blocksToMelt.clear();
        time = 0;

        antiCampRunnable.cancel();
        antiCampRunnable = null;
    }

    @EventHandler
    public void onPlayerDeath(PlayerDeathEvent event) {
        event.setDeathMessage(null);
        Bukkit.getScheduler().runTaskLater(TowerRun.getInstance(), () -> {
                    if (TowerRun.getTowerGenerator() != null) {
                        event.getEntity().teleport(TowerRun.getTowerGenerator().getSpawn());
                    } else {
                        event.getEntity().teleport(WorldConfig.SPAWN);
                    }
                }, 5
        );
        event.getEntity().setGameMode(GameMode.SPECTATOR);
        Bukkit.getOnlinePlayers().forEach(player -> {
            player.sendTitle("", TowerRun.getMessage().parse("PLAYER_DIED", player, event.getEntity().getPlayer().getName()), 10, 70, 20);
            player.playSound(player.getLocation(), Sound.ENTITY_WITHER_DEATH, 1, 1);
        });
    }

    @EventHandler
    public void onBlockBreak(BlockBreakEvent event) {
        event.setDropItems(false);
        if (!Config.DESTROYABLE_BLOCKS.contains(event.getBlock().getType())) {
            event.setCancelled(true);
        }
    }

    @EventHandler
    public void onKeyUse(PlayerInteractEvent event) {
        if (!event.hasItem()) return;
        if (event.getItem().getType() != Material.LEVER) return;
        if (!event.hasBlock()) return;
        if (event.getClickedBlock().getType() != Material.IRON_DOOR) return;
        if (event.getHand() == null) return;
        event.setCancelled(true);
        ItemStack itemStack = event.getItem();
        itemStack.setAmount(event.getItem().getAmount() - 1);
        switch (event.getHand()) {
            case OFF_HAND -> event.getPlayer().getInventory().setItemInOffHand(itemStack);
            case HAND -> event.getPlayer().getInventory().setItemInMainHand(itemStack);
        }
        event.getClickedBlock().breakNaturally();
    }

    @EventHandler
    public void onItemSpawn(ItemSpawnEvent event) {
        event.setCancelled(true);
    }

    @EventHandler
    public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
        if (event.getDamager().getType() == EntityType.PLAYER) {
            event.setCancelled(true);
        }
    }

    @EventHandler
    public void onPlayerInteract(PlayerInteractEvent event) {
        if (event.getPlayer().getGameMode() != GameMode.SURVIVAL) {
            return;
        }

        if (event.getAction() != Action.RIGHT_CLICK_BLOCK || !event.hasBlock()) {
            return;
        }

        if (event.getClickedBlock().getType() != Material.CHEST) {
            return;
        }

        Stream<Location> locations = TowerRun.getTowerGenerator() == null ? Arrays.stream(WorldConfig.KEYS) : TowerRun.getTowerGenerator().getKeys().stream();
        if (locations.noneMatch(location -> location.equals(event.getClickedBlock().getLocation()))) {
            event.getClickedBlock().setType(Material.AIR);
            return;
        }

        event.getPlayer().getInventory().addItem(new SWItem(Material.LEVER, TowerRun.getMessage().parse("KEY_NAME", event.getPlayer())).getItemStack());
        event.getClickedBlock().setType(Material.ENDER_CHEST);

        Bukkit.getOnlinePlayers().forEach(player -> {
            player.playSound(event.getPlayer().getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1, 1);
            player.sendTitle("", TowerRun.getMessage().parse("KEY_FOUND", player, event.getPlayer().getName()), 10, 70, 20);
        });
    }

    @EventHandler
    public void onBlockPhysics(BlockPhysicsEvent event) {
        if (!WorldConfig.MELTING) {
            return;
        }
        if (event.getSourceBlock().getType() != Material.LAVA) {
            return;
        }
        Block block = event.getSourceBlock();
        shouldMelt(block.getRelative(0, 1, 0));
        shouldMelt(block.getRelative(0, -1, 0));
        shouldMelt(block.getRelative(1, 0, 0));
        shouldMelt(block.getRelative(-1, 0, 0));
        shouldMelt(block.getRelative(0, 0, 1));
        shouldMelt(block.getRelative(0, 0, -1));
    }

    private static final Random RANDOM = new Random();

    private void shouldMelt(Block block) {
        if (block.getType().isBurnable()) return;
        if (block.getType().isAir()) return;
        if (block.isLiquid()) return;
        int meltingTime = (int) (block.getType().getHardness() * 48 * 20);
        switch (block.getType()) {
            case TINTED_GLASS:
                meltingTime = meltingTime * 2;
                break;
            case WHITE_STAINED_GLASS:
            case ORANGE_STAINED_GLASS:
            case MAGENTA_STAINED_GLASS:
            case LIGHT_BLUE_STAINED_GLASS:
            case YELLOW_STAINED_GLASS:
            case LIME_STAINED_GLASS:
            case PINK_STAINED_GLASS:
            case GRAY_STAINED_GLASS:
            case LIGHT_GRAY_STAINED_GLASS:
            case CYAN_STAINED_GLASS:
            case PURPLE_STAINED_GLASS:
            case BLUE_STAINED_GLASS:
            case BROWN_STAINED_GLASS:
            case GREEN_STAINED_GLASS:
            case RED_STAINED_GLASS:
            case BLACK_STAINED_GLASS:
            case WHITE_STAINED_GLASS_PANE:
            case ORANGE_STAINED_GLASS_PANE:
            case MAGENTA_STAINED_GLASS_PANE:
            case LIGHT_BLUE_STAINED_GLASS_PANE:
            case YELLOW_STAINED_GLASS_PANE:
            case LIME_STAINED_GLASS_PANE:
            case PINK_STAINED_GLASS_PANE:
            case GRAY_STAINED_GLASS_PANE:
            case LIGHT_GRAY_STAINED_GLASS_PANE:
            case CYAN_STAINED_GLASS_PANE:
            case PURPLE_STAINED_GLASS_PANE:
            case BLUE_STAINED_GLASS_PANE:
            case BROWN_STAINED_GLASS_PANE:
            case GREEN_STAINED_GLASS_PANE:
            case RED_STAINED_GLASS_PANE:
            case BLACK_STAINED_GLASS_PANE:
                meltingTime = meltingTime * 10;
            default:
                break;
        }
        Pos pos = new Pos(block.getLocation().getBlockX(), block.getLocation().getBlockY(), block.getLocation().getBlockZ());
        int delay = meltingTime + 1 + RANDOM.nextInt(30 * 20) - 30 * 10;
        if (delay < 0) delay = meltingTime + 1;
        blocksToMelt.putIfAbsent(pos, time + delay);
    }

    @EventHandler
    public void onEntityRegainHealth(EntityRegainHealthEvent event) {
        if (event.getEntityType() == EntityType.PLAYER) {
            event.setCancelled(true);
        }
    }
}
