package fr.luc.gamingcore.listener;

import fr.luc.gamingcore.Main;
import fr.luc.gamingcore.manager.GuildManager;
import fr.luc.gamingcore.model.Guild;
import fr.luc.gamingcore.model.GuildPermission;
import fr.luc.gamingcore.util.MessageManager;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Hanging;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.*;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.hanging.HangingBreakByEntityEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;

import java.util.Objects;

/**
 * Écouteur d'événements garantissant la protection territoriale complète des chunks de guilde :
 * anti-grief, anti-pvp, anti-pistons transfrontaliers, anti-fluides, anti-feu et protection des entités/conteneurs.
 */
public class TerritoryProtectionListener implements Listener {

    private final Main plugin;
    private final GuildManager guildManager;
    private final MessageManager messageManager;

    /**
     * Constructeur de l'écouteur de protection territoriale.
     *
     * @param plugin Instance principale du plugin.
     */
    public TerritoryProtectionListener(Main plugin) {
        this.plugin = plugin;
        this.guildManager = plugin.getGuildManager();
        this.messageManager = plugin.getMessageManager();
    }

    /**
     * Intercepte la destruction de blocs et vérifie si le joueur possède la permission de casser dans le claim.
     *
     * @param event L'événement BlockBreakEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onBlockBreak(BlockBreakEvent event) {
        Player player = event.getPlayer();
        if (player.hasPermission("guild.bypass")) {
            return;
        }

        Block block = event.getBlock();
        Guild guildAtBlock = guildManager.getGuildAt(block.getWorld().getName(), block.getX() >> 4, block.getZ() >> 4);
        if (guildAtBlock == null) {
            return; // Territoire libre
        }

        Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId());
        if (playerGuild == null || !playerGuild.getId().equals(guildAtBlock.getId())) {
            event.setCancelled(true);
            messageManager.sendActionBar(player, "protection.cannot_break", "{guild}", guildAtBlock.getFormattedName(), "{tag}", guildAtBlock.getFormattedTag());
            return;
        }

        // Le joueur est dans sa propre guilde : vérifier la permission BREAK
        if (!guildAtBlock.hasPermission(player.getUniqueId(), GuildPermission.BREAK)) {
            event.setCancelled(true);
            messageManager.sendActionBar(player, "protection.rank_cannot_break");
        }
    }

    /**
     * Intercepte la pose de blocs dans un territoire revendiqué.
     *
     * @param event L'événement BlockPlaceEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onBlockPlace(BlockPlaceEvent event) {
        Player player = event.getPlayer();
        if (player.hasPermission("guild.bypass")) {
            return;
        }

        Block block = event.getBlock();
        Guild guildAtBlock = guildManager.getGuildAt(block.getWorld().getName(), block.getX() >> 4, block.getZ() >> 4);
        if (guildAtBlock == null) {
            return;
        }

        Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId());
        if (playerGuild == null || !playerGuild.getId().equals(guildAtBlock.getId())) {
            event.setCancelled(true);
            messageManager.sendActionBar(player, "protection.cannot_build", "{guild}", guildAtBlock.getFormattedName(), "{tag}", guildAtBlock.getFormattedTag());
            return;
        }

        // Vérifier la permission BUILD
        if (!guildAtBlock.hasPermission(player.getUniqueId(), GuildPermission.BUILD)) {
            event.setCancelled(true);
            messageManager.sendActionBar(player, "protection.rank_cannot_build");
        }
    }

    /**
     * Intercepte les interactions avec les conteneurs (coffres, fours) et mécanismes (portes, boutons, leviers).
     *
     * @param event L'événement PlayerInteractEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onPlayerInteract(PlayerInteractEvent event) {
        if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getClickedBlock() == null) {
            return;
        }

        Player player = event.getPlayer();
        if (player.hasPermission("guild.bypass")) {
            return;
        }

        Block block = event.getClickedBlock();
        Guild guildAtBlock = guildManager.getGuildAt(block.getWorld().getName(), block.getX() >> 4, block.getZ() >> 4);
        if (guildAtBlock == null) {
            return;
        }

        Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId());
        boolean isSameGuild = playerGuild != null && playerGuild.getId().equals(guildAtBlock.getId());

        Material type = block.getType();
        boolean isContainer = isContainerMaterial(type);
        boolean isMechanism = isMechanismMaterial(type);

        if (!isSameGuild) {
            if (isContainer || isMechanism) {
                event.setCancelled(true);
                messageManager.sendActionBar(player, "protection.cannot_interact", "{guild}", guildAtBlock.getFormattedName(), "{tag}", guildAtBlock.getFormattedTag());
            }
            return;
        }

        // Vérification des permissions spécifiques de rang
        if (isContainer && !guildAtBlock.hasPermission(player.getUniqueId(), GuildPermission.CONTAINER)) {
            event.setCancelled(true);
            messageManager.sendActionBar(player, "protection.rank_cannot_container");
        } else if (isMechanism && !guildAtBlock.hasPermission(player.getUniqueId(), GuildPermission.INTERACT)) {
            event.setCancelled(true);
            messageManager.sendActionBar(player, "protection.rank_cannot_interact");
        }
    }

    /**
     * Intercepte et bloque le PvP dans tous les claims de guilde.
     *
     * @param event L'événement EntityDamageByEntityEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
        Entity victim = event.getEntity();
        Entity attacker = event.getDamager();

        Player attackerPlayer = null;
        if (attacker instanceof Player p) {
            attackerPlayer = p;
        } else if (attacker instanceof Projectile proj && proj.getShooter() instanceof Player p) {
            attackerPlayer = p;
        }

        // Si la victime est un joueur et qu'un joueur attaque
        if (victim instanceof Player && attackerPlayer != null) {
            if (attackerPlayer.hasPermission("guild.bypass")) return;

            Location victimLoc = victim.getLocation();
            Location attackerLoc = attackerPlayer.getLocation();

            boolean victimInGuild = guildManager.isChunkClaimed(victimLoc.getWorld().getName(), victimLoc.getBlockX() >> 4, victimLoc.getBlockZ() >> 4);
            boolean attackerInGuild = guildManager.isChunkClaimed(attackerLoc.getWorld().getName(), attackerLoc.getBlockX() >> 4, attackerLoc.getBlockZ() >> 4);

            if (victimInGuild || attackerInGuild) {
                event.setCancelled(true);
                messageManager.sendActionBar(attackerPlayer, "protection.pvp_denied");
            }
            return;
        }

        // Protéger les ArmorStands contre les dégâts des non-membres
        if (victim instanceof ArmorStand && attackerPlayer != null) {
            if (attackerPlayer.hasPermission("guild.bypass")) return;

            Location loc = victim.getLocation();
            Guild guildAtLoc = guildManager.getGuildAt(loc.getWorld().getName(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4);
            if (guildAtLoc != null) {
                Guild playerGuild = guildManager.getGuildByPlayer(attackerPlayer.getUniqueId());
                if (playerGuild == null || !playerGuild.getId().equals(guildAtLoc.getId()) || !guildAtLoc.hasPermission(attackerPlayer.getUniqueId(), GuildPermission.BREAK)) {
                    event.setCancelled(true);
                    messageManager.sendActionBar(attackerPlayer, "protection.hanging_denied");
                }
            }
        }
    }

    /**
     * Protège les cadres (ItemFrame) et tableaux (Painting) contre la casse par des non-membres.
     *
     * @param event L'événement HangingBreakByEntityEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onHangingBreak(HangingBreakByEntityEvent event) {
        if (event.getRemover() instanceof Player player) {
            if (player.hasPermission("guild.bypass")) return;

            Hanging hanging = event.getEntity();
            Location loc = hanging.getLocation();
            Guild guildAtLoc = guildManager.getGuildAt(loc.getWorld().getName(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4);
            if (guildAtLoc != null) {
                Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId());
                if (playerGuild == null || !playerGuild.getId().equals(guildAtLoc.getId()) || !guildAtLoc.hasPermission(player.getUniqueId(), GuildPermission.BREAK)) {
                    event.setCancelled(true);
                    messageManager.sendActionBar(player, "protection.hanging_denied");
                }
            }
        }
    }

    /**
     * Protège les interactions avec ArmorStands et cadres d'objets.
     *
     * @param event L'événement PlayerInteractEntityEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
        Entity target = event.getRightClicked();
        if (target instanceof ArmorStand || target instanceof Hanging) {
            Player player = event.getPlayer();
            if (player.hasPermission("guild.bypass")) return;

            Location loc = target.getLocation();
            Guild guildAtLoc = guildManager.getGuildAt(loc.getWorld().getName(), loc.getBlockX() >> 4, loc.getBlockZ() >> 4);
            if (guildAtLoc != null) {
                Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId());
                if (playerGuild == null || !playerGuild.getId().equals(guildAtLoc.getId()) || !guildAtLoc.hasPermission(player.getUniqueId(), GuildPermission.INTERACT)) {
                    event.setCancelled(true);
                    messageManager.sendActionBar(player, "protection.hanging_denied");
                }
            }
        }
    }

    /**
     * Empêche les pistons de pousser des blocs à travers les frontières d'un claim étranger.
     * Les pistons fonctionnant au sein d'une même guilde ou en territoire libre sont totalement autorisés.
     *
     * @param event L'événement BlockPistonExtendEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onPistonExtend(BlockPistonExtendEvent event) {
        Block piston = event.getBlock();
        String worldName = piston.getWorld().getName();
        Guild guildPiston = guildManager.getGuildAt(worldName, piston.getX() >> 4, piston.getZ() >> 4);

        if (event.getBlocks().isEmpty()) {
            // Piston qui pousse de l'air
            Block targetAir = piston.getRelative(event.getDirection());
            Guild guildTarget = guildManager.getGuildAt(worldName, targetAir.getX() >> 4, targetAir.getZ() >> 4);
            if (guildPiston == null && guildTarget != null) {
                event.setCancelled(true);
            } else if (guildPiston != null && guildTarget != null && !guildPiston.getId().equals(guildTarget.getId())) {
                event.setCancelled(true);
            }
            return;
        }

        for (Block block : event.getBlocks()) {
            Block targetBlock = block.getRelative(event.getDirection());
            Guild guildBlock = guildManager.getGuildAt(worldName, block.getX() >> 4, block.getZ() >> 4);
            Guild guildTarget = guildManager.getGuildAt(worldName, targetBlock.getX() >> 4, targetBlock.getZ() >> 4);

            if (guildPiston != null) {
                // Piston appartenant à une guilde
                if (guildBlock != null && !guildBlock.getId().equals(guildPiston.getId())) {
                    event.setCancelled(true);
                    return;
                }
                if (guildTarget != null && !guildTarget.getId().equals(guildPiston.getId())) {
                    event.setCancelled(true);
                    return;
                }
            } else {
                // Piston en zone libre (wilderness) : ne doit toucher à aucun territoire revendiqué
                if (guildBlock != null || guildTarget != null) {
                    event.setCancelled(true);
                    return;
                }
            }
        }
    }

    /**
     * Empêche les pistons collants de rétracter (tirer) des blocs depuis un claim étranger.
     *
     * @param event L'événement BlockPistonRetractEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onPistonRetract(BlockPistonRetractEvent event) {
        Block piston = event.getBlock();
        String worldName = piston.getWorld().getName();
        Guild guildPiston = guildManager.getGuildAt(worldName, piston.getX() >> 4, piston.getZ() >> 4);

        for (Block block : event.getBlocks()) {
            Guild guildBlock = guildManager.getGuildAt(worldName, block.getX() >> 4, block.getZ() >> 4);
            if (guildPiston != null) {
                if (guildBlock != null && !guildBlock.getId().equals(guildPiston.getId())) {
                    event.setCancelled(true);
                    return;
                }
            } else {
                if (guildBlock != null) {
                    event.setCancelled(true);
                    return;
                }
            }
        }
    }

    /**
     * Empêche l'écoulement des fluides (eau, lave) depuis l'extérieur vers un claim de guilde protégé.
     *
     * @param event L'événement BlockFromToEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onBlockFromTo(BlockFromToEvent event) {
        Block from = event.getBlock();
        Block to = event.getToBlock();

        int fromChunkX = from.getX() >> 4;
        int fromChunkZ = from.getZ() >> 4;
        int toChunkX = to.getX() >> 4;
        int toChunkZ = to.getZ() >> 4;

        if (fromChunkX == toChunkX && fromChunkZ == toChunkZ) {
            return; // Même chunk
        }

        String worldName = from.getWorld().getName();
        Guild guildFrom = guildManager.getGuildAt(worldName, fromChunkX, fromChunkZ);
        Guild guildTo = guildManager.getGuildAt(worldName, toChunkX, toChunkZ);

        if (guildTo != null) {
            if (guildFrom == null || !guildFrom.getId().equals(guildTo.getId())) {
                event.setCancelled(true);
            }
        }
    }

    /**
     * Empêche la destruction par combustion des blocs sur les territoires revendiqués.
     *
     * @param event L'événement BlockBurnEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onBlockBurn(BlockBurnEvent event) {
        Block block = event.getBlock();
        if (guildManager.isChunkClaimed(block.getWorld().getName(), block.getX() >> 4, block.getZ() >> 4)) {
            event.setCancelled(true);
        }
    }

    /**
     * Empêche la propagation du feu sur les territoires revendiqués.
     *
     * @param event L'événement BlockSpreadEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onBlockSpread(BlockSpreadEvent event) {
        Material source = event.getSource().getType();
        Material newState = event.getNewState().getType();

        if (source == Material.FIRE || source == Material.SOUL_FIRE || newState == Material.FIRE || newState == Material.SOUL_FIRE) {
            Block block = event.getBlock();
            if (guildManager.isChunkClaimed(block.getWorld().getName(), block.getX() >> 4, block.getZ() >> 4)) {
                event.setCancelled(true);
            }
        }
    }

    /**
     * Empêche l'allumage non autorisé par lave ou propagation de feu sur les claims.
     *
     * @param event L'événement BlockIgniteEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onBlockIgnite(BlockIgniteEvent event) {
        Block block = event.getBlock();
        if (!guildManager.isChunkClaimed(block.getWorld().getName(), block.getX() >> 4, block.getZ() >> 4)) {
            return;
        }

        BlockIgniteEvent.IgniteCause cause = event.getCause();
        if (cause == BlockIgniteEvent.IgniteCause.SPREAD || cause == BlockIgniteEvent.IgniteCause.LAVA || cause == BlockIgniteEvent.IgniteCause.FIREBALL) {
            event.setCancelled(true);
        }
    }

    /**
     * Protège les blocs de guilde contre les explosions de créatures ou de TNT.
     *
     * @param event L'événement EntityExplodeEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onEntityExplode(EntityExplodeEvent event) {
        if (!plugin.getConfig().getBoolean("protection.prevent_explosions", true)) {
            return;
        }
        event.blockList().removeIf(block -> guildManager.isChunkClaimed(
                block.getWorld().getName(),
                block.getX() >> 4,
                block.getZ() >> 4
        ));
    }

    /**
     * Protège les blocs de guilde contre les explosions de blocs (ex: cristaux de l'End, lits dans le Nether).
     *
     * @param event L'événement BlockExplodeEvent.
     */
    @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
    public void onBlockExplode(BlockExplodeEvent event) {
        if (!plugin.getConfig().getBoolean("protection.prevent_explosions", true)) {
            return;
        }
        event.blockList().removeIf(block -> guildManager.isChunkClaimed(
                block.getWorld().getName(),
                block.getX() >> 4,
                block.getZ() >> 4
        ));
    }

    /**
     * Détecte le passage d'une frontière de chunk et affiche une notification Action Bar lors de l'entrée/sortie de territoire.
     *
     * @param event L'événement PlayerMoveEvent.
     */
    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onPlayerMove(PlayerMoveEvent event) {
        if (!plugin.getConfig().getBoolean("protection.show_actionbar_on_move", true)) {
            return;
        }

        Location from = event.getFrom();
        Location to = event.getTo();
        if (to == null) return;

        int fromChunkX = from.getBlockX() >> 4;
        int fromChunkZ = from.getBlockZ() >> 4;
        int toChunkX = to.getBlockX() >> 4;
        int toChunkZ = to.getBlockZ() >> 4;

        if (fromChunkX == toChunkX && fromChunkZ == toChunkZ && from.getWorld().equals(to.getWorld())) {
            return; // Même chunk
        }

        String worldName = to.getWorld().getName();
        Guild previousGuild = guildManager.getGuildAt(from.getWorld().getName(), fromChunkX, fromChunkZ);
        Guild currentGuild = guildManager.getGuildAt(worldName, toChunkX, toChunkZ);

        if (Objects.equals(previousGuild, currentGuild)) {
            return;
        }

        Player player = event.getPlayer();
        if (currentGuild != null) {
            Guild playerGuild = guildManager.getGuildByPlayer(player.getUniqueId());
            boolean isOwn = playerGuild != null && playerGuild.getId().equals(currentGuild.getId());
            if (isOwn) {
                messageManager.sendActionBar(player, "protection.territory_enter_own", "{guild}", currentGuild.getFormattedName(), "{tag}", currentGuild.getFormattedTag());
            } else {
                messageManager.sendActionBar(player, "protection.territory_enter_other", "{guild}", currentGuild.getFormattedName(), "{tag}", currentGuild.getFormattedTag());
            }
        } else if (previousGuild != null) {
            messageManager.sendActionBar(player, "protection.territory_leave");
        }
    }

    // ==========================================
    // MÉTHODES UTILITAIRES
    // ==========================================

    private boolean isContainerMaterial(Material mat) {
        String name = mat.name();
        return name.contains("CHEST") || name.contains("BARREL") || name.contains("SHULKER") ||
                name.contains("FURNACE") || name.contains("HOPPER") || name.contains("DISPENSER") ||
                name.contains("DROPPER") || name.contains("BREWING_STAND");
    }

    private boolean isMechanismMaterial(Material mat) {
        String name = mat.name();
        return name.contains("DOOR") || name.contains("BUTTON") || name.contains("LEVER") ||
                name.contains("GATE") || name.contains("PRESSURE_PLATE");
    }
}
