package de.teleportsuite.bungee.manager;

import de.teleportsuite.bungee.TeleportSuiteBungee;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;

import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Receives plugin messages from Spigot servers on CHANNEL_TO_BUNGEE,
 * performs the cross-server operation (connect + forward), and sends
 * results back on CHANNEL_TO_SPIGOT.
 *
 * Payload identifiers (first UTF written by Spigot):
 *
 *   TP_PLAYER        – teleport sender to a named target player
 *   TP_POS           – teleport player to fixed coordinates on a server
 *   TP_HERE          – pull a remote player to the sender's location
 *   TP_ALL           – pull all network players to sender
 *   TPA_REQUEST      – forward "you have a TPA request" to target
 *   TPA_ACCEPT       – accept pending TPA; trigger the actual teleport
 *   TPA_DENY         – deny pending TPA; notify requester
 *   TPA_CANCEL       – requester cancels their own pending request
 *   TPA_EXPIRED      – timeout: notify both parties
 *   SEND_MSG         – route a chat message to a player on any server
 */
public class TeleportMessageListener implements Listener {

    // fromName → pending request
    private static final Map<String, PendingTpa> pendingTpa = new HashMap<>();

    private final TeleportSuiteBungee plugin;

    public TeleportMessageListener(TeleportSuiteBungee plugin) {
        this.plugin = plugin;
    }

    public static Map<String, PendingTpa> getPendingTpa() {
        return pendingTpa;
    }

    // -------------------------------------------------------------------------
    // Incoming messages from Spigot
    // -------------------------------------------------------------------------

    @EventHandler
    public void onPluginMessage(PluginMessageEvent event) throws IOException {
        if (event.isCancelled()) return;
        if (!(event.getSender() instanceof Server)) return;
        if (!event.getTag().equals(TeleportSuiteBungee.CHANNEL_TO_BUNGEE)) return;

        DataInputStream in = new DataInputStream(new ByteArrayInputStream(event.getData()));
        String task = in.readUTF();

        switch (task) {

            // ------------------------------------------------------------------
            // /tp <target>  →  teleport sender directly to target player
            // ------------------------------------------------------------------
            case "TP_PLAYER": {
                String senderName = in.readUTF();
                String targetName = in.readUTF();

                ProxiedPlayer sender = plugin.getProxy().getPlayer(senderName);
                ProxiedPlayer target = plugin.getProxy().getPlayer(targetName);

                if (sender == null) return;
                if (target == null) {
                    sendMsg(sender, "§cSpieler §6" + targetName + " §cnicht gefunden.");
                    return;
                }

                teleportPlayerToPlayer(sender, target, 50);
                break;
            }

            // ------------------------------------------------------------------
            // /tp <from> <to>  →  teleport another player to a target player
            // ------------------------------------------------------------------
            case "TP_PLAYER_TO_PLAYER": {
                String requesterName = in.readUTF();
                String fromName      = in.readUTF();
                String targetName    = in.readUTF();

                ProxiedPlayer requester = plugin.getProxy().getPlayer(requesterName);
                ProxiedPlayer from      = plugin.getProxy().getPlayer(fromName);
                ProxiedPlayer target    = plugin.getProxy().getPlayer(targetName);

                if (requester == null) return;
                if (from == null || target == null) {
                    sendMsg(requester, "§cEin Spieler wurde nicht gefunden.");
                    return;
                }

                teleportPlayerToPlayer(from, target, 50);
                break;
            }

            // ------------------------------------------------------------------
            // /tphere <target>  →  pull target to sender's location
            // ------------------------------------------------------------------
            case "TP_HERE": {
                String senderName = in.readUTF();
                String targetName = in.readUTF();

                ProxiedPlayer sender = plugin.getProxy().getPlayer(senderName);
                ProxiedPlayer target = plugin.getProxy().getPlayer(targetName);

                if (sender == null) return;
                if (target == null) {
                    sendMsg(sender, "§cSpieler §6" + targetName + " §cnicht gefunden.");
                    return;
                }

                teleportPlayerToPlayer(target, sender, 50);
                break;
            }

            // ------------------------------------------------------------------
            // /tpall  →  pull every player on the network to sender
            // ------------------------------------------------------------------
            case "TP_ALL": {
                String senderName = in.readUTF();
                ProxiedPlayer sender = plugin.getProxy().getPlayer(senderName);
                if (sender == null) return;

                for (ProxiedPlayer p : plugin.getProxy().getPlayers()) {
                    if (!p.getName().equals(senderName)) {
                        teleportPlayerToPlayer(p, sender, 50);
                    }
                }
                break;
            }

            // ------------------------------------------------------------------
            // /home, /warp, /spawn, /back, /savepoint, /portal, /tppos …
            // Spigot already knows the coordinates; we just switch the server
            // and tell that server where to put the player.
            // ------------------------------------------------------------------
            case "TP_POS": {
                String playerName = in.readUTF();
                String server     = in.readUTF();
                String world      = in.readUTF();
                double x = in.readDouble(), y = in.readDouble(), z = in.readDouble();
                float  yaw = in.readFloat(), pitch = in.readFloat();

                ProxiedPlayer player = plugin.getProxy().getPlayer(playerName);
                if (player == null) return;

                teleportPlayerToPosition(player, server, world, x, y, z, yaw, pitch, 100);
                break;
            }

            // ------------------------------------------------------------------
            // TPA: requester asks target for a teleport
            // ------------------------------------------------------------------
            case "TPA_REQUEST": {
                String fromName = in.readUTF();
                String toName   = in.readUTF();
                String type     = in.readUTF(); // TPTO or TPHERE
                String msgToTarget   = in.readUTF();
                String msgToRequester = in.readUTF();

                ProxiedPlayer from = plugin.getProxy().getPlayer(fromName);
                ProxiedPlayer to   = plugin.getProxy().getPlayer(toName);

                if (from == null) return;
                if (to == null) {
                    sendMsg(from, "§cSpieler §6" + toName + " §cnicht gefunden.");
                    return;
                }

                // Check for existing pending request
                if (pendingTpa.containsKey(fromName)) {
                    sendMsg(from, "§cDu hast bereits eine offene Anfrage.");
                    return;
                }

                pendingTpa.put(fromName, new PendingTpa(fromName, toName, PendingTpa.Type.valueOf(type)));

                // Notify both players — via their respective servers
                sendMsgToServer(from.getServer().getInfo(), fromName, msgToRequester);
                sendMsgToServer(to.getServer().getInfo(),   toName,   msgToTarget);
                break;
            }

            // ------------------------------------------------------------------
            // TPA: target accepts → trigger the actual teleport
            // ------------------------------------------------------------------
            case "TPA_ACCEPT": {
                String toName   = in.readUTF(); // player who accepted
                String fromName = in.readUTF(); // original requester (may be "nu" = any)
                String errMsg   = in.readUTF();

                PendingTpa tpa = resolvePending(fromName, toName);
                if (tpa == null) {
                    ProxiedPlayer to = plugin.getProxy().getPlayer(toName);
                    if (to != null) sendMsg(to, errMsg);
                    return;
                }

                pendingTpa.remove(tpa.getFromName());

                ProxiedPlayer from = plugin.getProxy().getPlayer(tpa.getFromName());
                ProxiedPlayer to   = plugin.getProxy().getPlayer(tpa.getToName());

                if (from == null || to == null) return;

                if (tpa.getType() == PendingTpa.Type.TPTO) {
                    teleportPlayerToPlayer(from, to, 50);
                } else {
                    teleportPlayerToPlayer(to, from, 50);
                }
                break;
            }

            // ------------------------------------------------------------------
            // TPA: target denies → notify requester
            // ------------------------------------------------------------------
            case "TPA_DENY": {
                String toName   = in.readUTF();
                String fromName = in.readUTF();
                String msg      = in.readUTF();

                PendingTpa tpa = resolvePending(fromName, toName);
                if (tpa == null) return;

                pendingTpa.remove(tpa.getFromName());

                ProxiedPlayer from = plugin.getProxy().getPlayer(tpa.getFromName());
                ProxiedPlayer to   = plugin.getProxy().getPlayer(tpa.getToName());

                if (from != null) sendMsg(from, msg);
                if (to   != null) sendMsg(to,   msg);
                break;
            }

            // ------------------------------------------------------------------
            // TPA: requester cancels their own request
            // ------------------------------------------------------------------
            case "TPA_CANCEL": {
                String fromName = in.readUTF();
                String msgFrom  = in.readUTF();
                String msgTo    = in.readUTF();

                PendingTpa tpa = pendingTpa.remove(fromName);
                if (tpa == null) return;

                ProxiedPlayer from = plugin.getProxy().getPlayer(fromName);
                ProxiedPlayer to   = plugin.getProxy().getPlayer(tpa.getToName());

                if (from != null) sendMsg(from, msgFrom);
                if (to   != null) sendMsg(to,   msgTo);
                break;
            }

            // ------------------------------------------------------------------
            // TPA: timeout — remove pending and notify both sides
            // ------------------------------------------------------------------
            case "TPA_EXPIRED": {
                String fromName = in.readUTF();
                String toName   = in.readUTF();
                String msgFrom  = in.readUTF();
                String msgTo    = in.readUTF();

                pendingTpa.remove(fromName);

                ProxiedPlayer from = plugin.getProxy().getPlayer(fromName);
                ProxiedPlayer to   = plugin.getProxy().getPlayer(toName);

                if (from != null) sendMsg(from, msgFrom);
                if (to   != null) sendMsg(to,   msgTo);
                break;
            }

            // ------------------------------------------------------------------
            // Generic cross-server message routing
            // ------------------------------------------------------------------
            case "SEND_MSG": {
                String targetName = in.readUTF();
                String message    = in.readUTF();
                ProxiedPlayer target = plugin.getProxy().getPlayer(targetName);
                if (target != null) sendMsg(target, message);
                break;
            }

            default:
                plugin.getLogger().warning("Unbekannter Task: " + task);
        }
    }

    // -------------------------------------------------------------------------
    // Core teleport helpers — the BTM way
    // -------------------------------------------------------------------------

    /**
     * Moves {@code sender} to {@code target}'s current server (if needed),
     * then after a short delay tells the target server to do the final
     * player.teleport(target) call via TP_PLAYERTOPLAYER.
     *
     * This is done entirely on the Bungee side:
     *   1. sender.connect(target.server)   — BungeeCord switches the server
     *   2. delay ms later: server.sendData(TP_PLAYERTOPLAYER, ...)
     *      → Spigot polls until both players are on the same server and teleports
     */
    private void teleportPlayerToPlayer(ProxiedPlayer sender, ProxiedPlayer target, int delayMs) {
        plugin.getProxy().getScheduler().schedule(plugin, () -> {
            if (sender == null || target == null) return;
            if (sender.getServer() == null || target.getServer() == null) return;

            // Switch server if needed
            if (!sender.getServer().getInfo().getName()
                    .equals(target.getServer().getInfo().getName())) {
                sender.connect(target.getServer().getInfo());
            }

            // Tell the target server to teleport sender → target
            try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
                 DataOutputStream out = new DataOutputStream(bos)) {
                out.writeUTF("TP_PLAYERTOPLAYER");
                out.writeUTF(sender.getName());
                out.writeUTF(target.getName());
                target.getServer().sendData(TeleportSuiteBungee.CHANNEL_TO_SPIGOT, bos.toByteArray());
            } catch (IOException e) {
                plugin.getLogger().warning("TP_PLAYERTOPLAYER Fehler: " + e.getMessage());
            }
        }, delayMs, TimeUnit.MILLISECONDS);
    }

    /**
     * Moves {@code player} to {@code server}, then after a delay sends
     * TP_PLAYERTOPOSITION so Spigot teleports them to exact coordinates.
     */
    private void teleportPlayerToPosition(ProxiedPlayer player, String server,
                                          String world, double x, double y, double z,
                                          float yaw, float pitch, int delayMs) {
        if (!plugin.getProxy().getServers().containsKey(server)) {
            sendMsg(player, "§cServer §6" + server + " §cnicht gefunden.");
            return;
        }

        // Switch server if needed
        if (!player.getServer().getInfo().getName().equals(server)) {
            player.connect(plugin.getProxy().getServerInfo(server));
        }

        // After delay, tell the (now correct) server where to put the player
        plugin.getProxy().getScheduler().schedule(plugin, () -> {
            if (player.getServer() == null) return;
            try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
                 DataOutputStream out = new DataOutputStream(bos)) {
                out.writeUTF("TP_PLAYERTOPOSITION");
                out.writeUTF(player.getName());
                out.writeUTF(world);
                out.writeDouble(x);
                out.writeDouble(y);
                out.writeDouble(z);
                out.writeFloat(yaw);
                out.writeFloat(pitch);
                player.getServer().sendData(TeleportSuiteBungee.CHANNEL_TO_SPIGOT, bos.toByteArray());
            } catch (IOException e) {
                plugin.getLogger().warning("TP_PLAYERTOPOSITION Fehler: " + e.getMessage());
            }
        }, delayMs, TimeUnit.MILLISECONDS);
    }

    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    /** Resolves which pending TPA entry matches an accept/deny. */
    private PendingTpa resolvePending(String fromName, String toName) {
        // Exact match
        if (!fromName.equals("nu") && pendingTpa.containsKey(fromName)) {
            return pendingTpa.get(fromName);
        }
        // Find by target name only
        for (PendingTpa tpa : pendingTpa.values()) {
            if (tpa.getToName().equalsIgnoreCase(toName)) {
                return tpa;
            }
        }
        return null;
    }

    /** Sends a formatted chat message directly to a ProxiedPlayer. */
    private void sendMsg(ProxiedPlayer player, String message) {
        player.sendMessage(net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', message));
    }

    /**
     * Routes a chat message to a named player via their Spigot server.
     * The Spigot side handles the actual sendMessage() call.
     */
    private void sendMsgToServer(net.md_5.bungee.api.config.ServerInfo server,
                                 String playerName, String message) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             DataOutputStream out = new DataOutputStream(bos)) {
            out.writeUTF("SEND_MSG");
            out.writeUTF(playerName);
            out.writeUTF(message);
            server.sendData(TeleportSuiteBungee.CHANNEL_TO_SPIGOT, bos.toByteArray());
        } catch (IOException e) {
            plugin.getLogger().warning("SEND_MSG Fehler: " + e.getMessage());
        }
    }
}
