/*
 *   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.fightsystem.ai;

import com.sk89q.worldedit.extent.clipboard.Clipboard;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.fight.JoinRequest;
import de.steamwar.fightsystem.listener.Chat;
import de.steamwar.fightsystem.record.GlobalRecorder;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.OneShotStateDependent;
import de.steamwar.fightsystem.utils.Region;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Note;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Lectern;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.FaceAttachable;
import org.bukkit.block.data.Openable;
import org.bukkit.block.data.Powerable;
import org.bukkit.block.data.type.Comparator;
import org.bukkit.block.data.type.NoteBlock;
import org.bukkit.block.data.type.Repeater;
import org.bukkit.block.data.type.Switch;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Villager;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;

import java.util.*;
import java.util.logging.Level;

public abstract class AI {

    public static final int MOVEMENT_DELAY = 4;
    public static final double INTERACTION_RANGE = 5.0;

    private static final Map<UUID, AI> ais = new HashMap<>();

    public static void printPos() {
        ais.values().forEach(ai -> ai.chat(ai.entity.isValid() + " " + ai.entity.isDead() + " " + ai.entity.getLocation()));
    }

    static {
        new OneShotStateDependent(ArenaMode.AntiReplay, FightState.Spectate, () -> {
            ais.values().forEach(AI::stop);
            ais.clear();
        });
    }

    public static AI getAI(UUID uuid) {
        return ais.get(uuid);
    }

    @Getter
    protected final FightTeam team;
    @Getter
    private final LivingEntity entity;
    private final BukkitTask task;
    private final Queue<Action> queue = new ArrayDeque<>();

    protected AI(FightTeam team, SteamwarUser user) {
        this.team = team;

        entity = (LivingEntity) Config.world.spawnEntity(Config.SpecSpawn, EntityType.VILLAGER);
        entity.setCustomName(user.getUserName());
        ((Villager) entity).setAware(false);

        task = Bukkit.getScheduler().runTaskTimer(FightSystem.getPlugin(), this::run, 1, 1);
        ais.put(entity.getUniqueId(), this);
        team.addMember(entity, user);

        if (FightState.Schem.contains(FightState.getFightState())) {
            Bukkit.getScheduler().runTask(FightSystem.getPlugin(), () -> schematic(team.getClipboard()));
        }
    }

    public abstract SchematicNode chooseSchematic();

    public abstract void schematic(Clipboard clipboard);

    public boolean acceptJoinRequest(JoinRequest.Enquirer enquirer, FightTeam team) {
        return true;
    }

    protected abstract void plan();

    public void stop() {
        if (!entity.isDead()) entity.remove();
        if (!task.isCancelled()) task.cancel();
    }

    public void setReady() {
        if (FightState.getFightState() != FightState.POST_SCHEM_SETUP) return;
        if (team.getLeader().getEntity() != entity) return;
        team.setReady(true);
    }

    public void chat(String message) {
        FightSystem.getPlugin().getLogger().log(Level.INFO, () -> entity.getName() + "» " + message);
        Chat.broadcastChat("PARTICIPANT_CHAT", team.getColoredName(), entity.getName(), message);
    }

    public Vector getPosition() {
        Location location = entity.getLocation();
        Region extend = team.getExtendRegion();
        if (Fight.getUnrotated() == team) {
            return new Vector(
                    location.getX() - extend.getMinX(),
                    location.getY() - team.getSchemRegion().getMinY(),
                    location.getZ() - extend.getMinZ()
            );
        } else {
            return new Vector(
                    extend.getMaxX() - location.getX(),
                    location.getY() - team.getSchemRegion().getMinY(),
                    extend.getMaxZ() - location.getZ()
            );
        }
    }

    public Material getBlock(Vector pos) {
        queue.add(new Action(1));
        return translate(pos).getBlock().getType();
    }

    public BlockData getBlockData(Vector pos) {
        queue.add(new Action(1));
        return translate(pos).getBlock().getBlockData();
    }

    public void setTNT(Vector pos) {
        queue.add(new Action(1) {
            @Override
            public void run() {
                if (FightState.getFightState() != FightState.RUNNING) return;

                Location location = translate(pos);
                if (interactionDistanceViolation(location)) {
                    chat("InteractionDistanceViolation: setTNT");
                    return;
                }

                Block block = location.getBlock();
                if (block.getType() == Material.AIR) block.setType(Material.TNT);
            }
        });
    }

    public void interact(Vector pos) {
        queue.add(new Action(1) {
            @Override
            public void run() {
                Location location = translate(pos);
                if (interactionDistanceViolation(location)) {
                    chat("InteractionDistanceViolation: interact");
                    return;
                }

                interact(location.getBlock());
            }
        });
    }

    public void interact(Vector pos, int n) {
        queue.add(new Action(1) {
            @Override
            public void run() {
                Location location = translate(pos);
                if (interactionDistanceViolation(location)) return;
                Block block = location.getBlock();
                BlockData data = block.getBlockData();
                if (data instanceof Repeater) {
                    Repeater repeater = (Repeater) data;
                    repeater.setDelay(n);
                } else if (data instanceof Lectern) {
                    Lectern lectern = (Lectern) data;
                    lectern.setPage(n);
                }
                block.setBlockData(data);
            }
        });
    }

    public void move(Vector pos) {
        queue.add(new Action(MOVEMENT_DELAY) {
            @Override
            public void run() {
                Location location = entity.getLocation();
                if (!entity.isOnGround() && location.getBlock().getType() != Material.LADDER) {
                    FightSystem.getPlugin().getLogger().log(Level.INFO, "Entity falling");
                    return;
                }

                Location target = translate(pos);
                if (Math.abs(location.getX() - target.getX()) > 1.0 || Math.abs(location.getY() - target.getY()) > 1.5 || Math.abs(location.getZ() - target.getZ()) > 1.0) {
                    FightSystem.getPlugin().getLogger().log(Level.INFO, () -> entity.getName() + ": Overdistance movement " + location.toVector() + " " + target.toVector());
                    return;
                }

                if (!team.getFightPlayer(entity).canEntern() && !team.getExtendRegion().inRegion(target))
                    return;

                if (!entity.teleport(target, PlayerTeleportEvent.TeleportCause.PLUGIN)) {
                    FightSystem.getPlugin().getLogger().log(Level.INFO, "Entity not teleported: " + entity.isValid());
                }

                GlobalRecorder.getInstance().entityMoves(entity);
            }
        });
    }

    private boolean interactionDistanceViolation(Location location) {
        return location.distance(entity.getEyeLocation()) > INTERACTION_RANGE;
    }

    private void interact(Block block) {
        BlockData data = block.getBlockData(); //TODO only 1.14+ compatible at the moment
        if (data instanceof NoteBlock) {
            NoteBlock noteBlock = (NoteBlock) data;
            Note note = noteBlock.getNote();
            noteBlock.setNote(note.isSharped() ? note.flattened() : note.sharped());
        } else if (data instanceof Openable) {
            Openable openable = (Openable) data;
            openable.setOpen(!openable.isOpen());
        } else if (data instanceof Comparator) {
            Comparator comparator = (Comparator) data;
            comparator.setMode(Comparator.Mode.values()[1 - comparator.getMode().ordinal()]);
        } else if (data instanceof Powerable) {
            Material type = block.getType();
            Powerable powerable = (Powerable) data;
            boolean isPowered = powerable.isPowered();

            if (type.name().endsWith("BUTTON")) {
                if (isPowered) return;

                Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> {
                    if (!block.getType().name().endsWith("BUTTON")) return;

                    powerable.setPowered(false);
                    block.setBlockData(powerable);
                    updateButton(block);
                }, type.name().endsWith("STONE_BUTTON") ? 20 : 30);
            }

            powerable.setPowered(!isPowered);
        }
        block.setBlockData(data);
        if (data instanceof Switch) {
            updateButton(block);
        }
    }

    private void updateButton(Block block) {
        Switch sw = (Switch) block.getBlockData();
        FaceAttachable.AttachedFace face = sw.getAttachedFace();
        if (face == FaceAttachable.AttachedFace.FLOOR) {
            update(block.getRelative(BlockFace.DOWN));
        } else if (face == FaceAttachable.AttachedFace.CEILING) {
            update(block.getRelative(BlockFace.UP));
        } else {
            update(block.getRelative(sw.getFacing().getOppositeFace()));
        }
    }

    private void update(Block block) {
        BlockData data = block.getBlockData();
        block.setType(Material.BARRIER);
        block.setBlockData(data);
    }

    private void run() {
        if (queue.isEmpty()) {
            try {
                plan();
            } catch (Throwable t) {
                stop();
                throw t;
            }
        }

        if (!queue.isEmpty() && --queue.peek().delay == 0) queue.poll().run();
    }

    public Location translate(Vector pos) {
        Region extend = team.getExtendRegion();
        if (Fight.getUnrotated() == team) {
            return new Location(
                    Config.world,
                    pos.getX() + extend.getMinX(),
                    pos.getY() + team.getSchemRegion().getMinY(),
                    pos.getZ() + extend.getMinZ()
            );
        } else {
            return new Location(
                    Config.world,
                    extend.getMaxX() - pos.getX(),
                    pos.getY() + team.getSchemRegion().getMinY(),
                    extend.getMaxZ() - pos.getZ()
            );
        }
    }

    private static class Action {
        private int delay;

        public Action(int delay) {
            this.delay = delay;
        }

        public void run() {
        }
    }
}
