package de.teleportsuite.bungeemessaging;

import de.teleportsuite.TeleportSuite;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

/**
 * Handles BungeeCord Plugin Messaging.
 * Sends players to other servers and transmits target coordinates
 * so a receiving TeleportSuite instance can teleport them on arrival.
 *
 * Channel "teleportsuite:tp" payload format (DataOutputStream):
 *   String  targetPlayer
 *   String  world
 *   Double  x, y, z
 *   Float   yaw, pitch
 */
public class BungeeMessenger implements PluginMessageListener {

    private static final String BUNGEE_CHANNEL = "BungeeCord";
    private static final String TS_CHANNEL     = "teleportsuite:tp";
    private final TeleportSuite plugin;
    private final Map<String, List<Consumer<String>>> pendingServerLookups = new HashMap<>();
    private final Map<String, List<Consumer<List<String>>>> pendingPlayerListLookups = new HashMap<>();

    public BungeeMessenger(TeleportSuite plugin) { this.plugin = plugin; }

    public void register() {
        plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, BUNGEE_CHANNEL);
        plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, TS_CHANNEL);
        plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, BUNGEE_CHANNEL, this);
        plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, TS_CHANNEL, this);
    }

    public void unregister() {
        plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin);
        plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin);
    }

    public void requestPlayerServer(Player sender, String targetPlayer, Consumer<String> callback) {
        if (sender == null || !sender.isOnline()) {
            callback.accept(null);
            return;
        }

        String key = targetPlayer.toLowerCase(Locale.ROOT);
        pendingServerLookups.computeIfAbsent(key, k -> new ArrayList<>()).add(callback);

        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             DataOutputStream out = new DataOutputStream(bos)) {
            out.writeUTF("GetPlayerServer");
            out.writeUTF(targetPlayer);
            sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
        } catch (IOException e) {
            plugin.getLogger().warning("GetPlayerServer Fehler: " + e.getMessage());
            flushServerLookupCallbacks(key, null);
        }
    }

    public void requestPlayerList(Player sender, String serverScope, Consumer<List<String>> callback) {
        if (sender == null || !sender.isOnline()) {
            callback.accept(List.of());
            return;
        }

        String scope = serverScope == null || serverScope.isBlank() ? "ALL" : serverScope;
        String key = scope.toUpperCase(Locale.ROOT);
        pendingPlayerListLookups.computeIfAbsent(key, k -> new ArrayList<>()).add(callback);

        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             DataOutputStream out = new DataOutputStream(bos)) {
            out.writeUTF("PlayerList");
            out.writeUTF(scope);
            sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
        } catch (IOException e) {
            plugin.getLogger().warning("PlayerList Fehler: " + e.getMessage());
            flushPlayerListCallbacks(key, List.of());
        }
    }

    public void teleportAllPlayersToLocalPlayer(Player requester) {
        String requesterName = requester.getName();
        requestPlayerList(requester, "ALL", players -> {
            if (players == null || players.isEmpty()) {
                requester.sendMessage("§cKeine Spieler gefunden.");
                return;
            }

            Set<String> seen = new HashSet<>();
            for (String playerName : players) {
                if (playerName == null || playerName.isBlank()) continue;
                if (playerName.equalsIgnoreCase(requesterName)) continue;
                if (!seen.add(playerName.toLowerCase(Locale.ROOT))) continue;
                teleportPlayerToLocalPlayer(requester, playerName, requesterName);
            }

            requester.sendMessage("§aAlle verfügbaren Spieler im Netzwerk werden zu dir teleportiert.");
        });
    }

    public void teleportToPlayer(Player requester, String targetPlayerName) {
        requestPlayerServer(requester, targetPlayerName, server -> {
            if (server == null || server.isBlank()) {
                requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetPlayerName));
                return;
            }

            String localServer = plugin.getConfigManager().getServerName();
            if (server.equalsIgnoreCase(localServer)) {
                Player target = plugin.getServer().getPlayerExact(targetPlayerName);
                if (target == null || !target.isOnline()) {
                    requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetPlayerName));
                    return;
                }
                plugin.getTeleportManager().teleport(
                    requester,
                    new de.teleportsuite.models.TeleportLocation(target.getLocation(), localServer)
                );
                return;
            }

            sendPlayerToPlayerPayload(requester, requester.getName(), targetPlayerName, server);
            connectPlayerOnly(requester, server);
            requester.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
        });
    }

    public void teleportPlayerToLocalPlayer(Player requester, String moverPlayerName, String localTargetPlayerName) {
        requestPlayerServer(requester, moverPlayerName, moverServer -> {
            if (moverServer == null || moverServer.isBlank()) {
                requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", moverPlayerName));
                return;
            }

            String localServer = plugin.getConfigManager().getServerName();
            if (moverServer.equalsIgnoreCase(localServer)) {
                Player mover = plugin.getServer().getPlayerExact(moverPlayerName);
                Player target = plugin.getServer().getPlayerExact(localTargetPlayerName);
                if (mover == null || !mover.isOnline()) {
                    requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", moverPlayerName));
                    return;
                }
                if (target == null || !target.isOnline()) {
                    requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", localTargetPlayerName));
                    return;
                }
                plugin.getTeleportManager().teleport(
                    mover,
                    new de.teleportsuite.models.TeleportLocation(target.getLocation(), localServer)
                );
                return;
            }

            sendPlayerToPlayerPayload(requester, moverPlayerName, localTargetPlayerName, localServer);
            connectOtherPlayer(requester, moverPlayerName, localServer);
            requester.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
        });
    }

    public void teleportAnyPlayerToAnyPlayer(Player requester, String moverPlayerName, String targetPlayerName) {
        requestPlayerServer(requester, targetPlayerName, targetServer -> {
            if (targetServer == null || targetServer.isBlank()) {
                requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetPlayerName));
                return;
            }

            requestPlayerServer(requester, moverPlayerName, moverServer -> {
                if (moverServer == null || moverServer.isBlank()) {
                    requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", moverPlayerName));
                    return;
                }

                String localServer = plugin.getConfigManager().getServerName();
                if (moverServer.equalsIgnoreCase(localServer) && targetServer.equalsIgnoreCase(localServer)) {
                    Player mover = plugin.getServer().getPlayerExact(moverPlayerName);
                    Player target = plugin.getServer().getPlayerExact(targetPlayerName);
                    if (mover == null || !mover.isOnline()) {
                        requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", moverPlayerName));
                        return;
                    }
                    if (target == null || !target.isOnline()) {
                        requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetPlayerName));
                        return;
                    }
                    plugin.getTeleportManager().teleport(
                        mover,
                        new de.teleportsuite.models.TeleportLocation(target.getLocation(), localServer)
                    );
                    return;
                }

                sendPlayerToPlayerPayload(requester, moverPlayerName, targetPlayerName, targetServer);
                if (!moverServer.equalsIgnoreCase(targetServer)) {
                    connectOtherPlayer(requester, moverPlayerName, targetServer);
                }
                requester.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
            });
        });
    }

    public void sendTpaRequestToPlayer(Player sender, String targetPlayerName) {
        sendForwardToPlayer(sender, targetPlayerName, out -> {
            out.writeUTF("TPA_REQUEST");
            out.writeUTF(sender.getName());
            out.writeUTF(targetPlayerName);
        });
    }

    public void sendTpaAcceptToPlayer(Player sender, String requesterName) {
        sendForwardToPlayer(sender, requesterName, out -> {
            out.writeUTF("TPA_ACCEPT");
            out.writeUTF(requesterName);
            out.writeUTF(sender.getName());
        });
    }

    public void sendTpaDenyToPlayer(Player sender, String requesterName) {
        sendForwardToPlayer(sender, requesterName, out -> {
            out.writeUTF("TPA_DENY");
            out.writeUTF(requesterName);
            out.writeUTF(sender.getName());
        });
    }

    public void sendTpaExpiredToPlayer(Player sender, String targetPlayerName) {
        sendForwardToPlayer(sender, targetPlayerName, out -> {
            out.writeUTF("TPA_EXPIRED");
            out.writeUTF(sender.getName());
            out.writeUTF(targetPlayerName);
        });
    }

    /**
     * Connect a player to another BungeeCord server.
     * Also sends a plugin message so the target server knows where to teleport the player.
     */
    public void connectToServer(Player player, String server, String world, double x, double y, double z, float yaw, float pitch) {
        // 1) Notify the target server about the pending teleport
        sendTeleportPayload(player, player.getName(), server, world, x, y, z, yaw, pitch);

        // 2) Switch server via BungeeCord
        connectPlayerOnly(player, server);
    }

    private void connectPlayerOnly(Player player, String server) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             DataOutputStream out = new DataOutputStream(bos)) {
            out.writeUTF("Connect");
            out.writeUTF(server);
            player.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
        } catch (IOException e) {
            plugin.getLogger().warning("BungeeCord Connect Fehler: " + e.getMessage());
        }
    }

    private void connectOtherPlayer(Player anchor, String playerName, String server) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             DataOutputStream out = new DataOutputStream(bos)) {
            out.writeUTF("ConnectOther");
            out.writeUTF(playerName);
            out.writeUTF(server);
            anchor.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
        } catch (IOException e) {
            plugin.getLogger().warning("BungeeCord ConnectOther Fehler: " + e.getMessage());
        }
    }

    private void sendTeleportPayload(Player sender, String targetPlayer, String server, String world,
                                     double x, double y, double z, float yaw, float pitch) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             DataOutputStream out = new DataOutputStream(bos)) {
            out.writeUTF("Forward");
            out.writeUTF(server);
            out.writeUTF(TS_CHANNEL);
            // Sub-payload
            ByteArrayOutputStream sub = new ByteArrayOutputStream();
            DataOutputStream subOut = new DataOutputStream(sub);
            subOut.writeUTF("LOCATION");
            subOut.writeUTF(targetPlayer);
            subOut.writeUTF(world);
            subOut.writeDouble(x);
            subOut.writeDouble(y);
            subOut.writeDouble(z);
            subOut.writeFloat(yaw);
            subOut.writeFloat(pitch);
            byte[] subBytes = sub.toByteArray();
            out.writeShort(subBytes.length);
            out.write(subBytes);
            sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
        } catch (IOException e) {
            plugin.getLogger().warning("TS Forward Fehler: " + e.getMessage());
        }
    }

    private void sendPlayerToPlayerPayload(Player sender, String moverPlayer, String targetPlayer, String targetServer) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             DataOutputStream out = new DataOutputStream(bos)) {
            out.writeUTF("Forward");
            out.writeUTF(targetServer);
            out.writeUTF(TS_CHANNEL);

            ByteArrayOutputStream sub = new ByteArrayOutputStream();
            DataOutputStream subOut = new DataOutputStream(sub);
            subOut.writeUTF("TP_TO_PLAYER");
            subOut.writeUTF(moverPlayer);
            subOut.writeUTF(targetPlayer);

            byte[] subBytes = sub.toByteArray();
            out.writeShort(subBytes.length);
            out.write(subBytes);
            sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
        } catch (IOException e) {
            plugin.getLogger().warning("TS Player->Player Forward Fehler: " + e.getMessage());
        }
    }

    private void sendForwardToPlayer(Player sender, String targetPlayer, IOConsumer<DataOutputStream> payloadWriter) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             DataOutputStream out = new DataOutputStream(bos)) {
            out.writeUTF("ForwardToPlayer");
            out.writeUTF(targetPlayer);
            out.writeUTF(TS_CHANNEL);

            ByteArrayOutputStream sub = new ByteArrayOutputStream();
            DataOutputStream subOut = new DataOutputStream(sub);
            payloadWriter.accept(subOut);

            byte[] subBytes = sub.toByteArray();
            out.writeShort(subBytes.length);
            out.write(subBytes);
            sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
        } catch (IOException e) {
            plugin.getLogger().warning("TS ForwardToPlayer Fehler: " + e.getMessage());
        }
    }

    private void schedulePlayerToPlayerTeleport(String moverName, String targetName) {
        int maxAttempts = 100;
        final int[] attempts = {0};
        final int[] taskId = {0};

        taskId[0] = plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, () -> {
            attempts[0]++;
            Player mover = plugin.getServer().getPlayerExact(moverName);
            Player target = plugin.getServer().getPlayerExact(targetName);

            if (mover != null && mover.isOnline() && target != null && target.isOnline()) {
                plugin.getTeleportManager().teleport(
                    mover,
                    new de.teleportsuite.models.TeleportLocation(target.getLocation(), plugin.getConfigManager().getServerName()),
                    false
                );
                plugin.getServer().getScheduler().cancelTask(taskId[0]);
                return;
            }

            if (attempts[0] >= maxAttempts) {
                plugin.getServer().getScheduler().cancelTask(taskId[0]);
            }
        }, 10L, 10L);
    }

    private void flushServerLookupCallbacks(String key, String server) {
        List<Consumer<String>> callbacks = pendingServerLookups.remove(key);
        if (callbacks == null) return;
        for (Consumer<String> callback : callbacks) {
            callback.accept(server);
        }
    }

    private void flushPlayerListCallbacks(String key, List<String> players) {
        List<Consumer<List<String>>> callbacks = pendingPlayerListLookups.remove(key);
        if (callbacks == null) return;
        for (Consumer<List<String>> callback : callbacks) {
            callback.accept(players);
        }
    }

    @Override
    public void onPluginMessageReceived(String channel, Player player, byte[] message) {
        if (channel.equals(BUNGEE_CHANNEL)) {
            handleBungeeMessage(message);
            return;
        }

        if (channel.equals(TS_CHANNEL)) {
            handleTeleportSuiteMessage(message);
        }
    }

    private void handleBungeeMessage(byte[] message) {
        try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) {
            String subChannel = in.readUTF();
            if ("GetPlayerServer".equals(subChannel)) {
                String playerName = in.readUTF();
                String serverName = in.readUTF();
                if ("null".equalsIgnoreCase(serverName) || serverName.isBlank()) {
                    serverName = null;
                }
                flushServerLookupCallbacks(playerName.toLowerCase(Locale.ROOT), serverName);
                return;
            }

            if ("PlayerList".equals(subChannel)) {
                String scope = in.readUTF();
                String playerCsv = in.readUTF();
                List<String> players = new ArrayList<>();
                if (playerCsv != null && !playerCsv.isBlank()) {
                    for (String name : playerCsv.split(",")) {
                        String trimmed = name.trim();
                        if (!trimmed.isEmpty()) players.add(trimmed);
                    }
                }
                flushPlayerListCallbacks(scope.toUpperCase(Locale.ROOT), players);
                return;
            }
        } catch (IOException e) {
            plugin.getLogger().warning("Fehler beim Lesen der Bungee-Nachricht: " + e.getMessage());
        }
    }

    private void handleTeleportSuiteMessage(byte[] message) {
        try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) {
            String payloadType = in.readUTF();

            if ("LOCATION".equals(payloadType)) {
                String targetName = in.readUTF();
                String world = in.readUTF();
                double x = in.readDouble(), y = in.readDouble(), z = in.readDouble();
                float yaw = in.readFloat(), pitch = in.readFloat();

                plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
                    Player target = plugin.getServer().getPlayerExact(targetName);
                    if (target == null || !target.isOnline()) return;
                    de.teleportsuite.models.TeleportLocation loc =
                        new de.teleportsuite.models.TeleportLocation(world, x, y, z, yaw, pitch,
                            plugin.getConfigManager().getServerName());
                    plugin.getTeleportManager().teleport(target, loc, false);
                }, 20L);
                return;
            }

            if ("TP_TO_PLAYER".equals(payloadType)) {
                String moverName = in.readUTF();
                String targetName = in.readUTF();
                schedulePlayerToPlayerTeleport(moverName, targetName);
                return;
            }

            if ("TPA_REQUEST".equals(payloadType)) {
                String requesterName = in.readUTF();
                String targetName = in.readUTF();
                plugin.getTeleportManager().receiveCrossServerTpaRequest(requesterName, targetName);
                return;
            }

            if ("TPA_ACCEPT".equals(payloadType)) {
                String requesterName = in.readUTF();
                String targetName = in.readUTF();
                plugin.getTeleportManager().receiveCrossServerTpaAccepted(requesterName, targetName);
                return;
            }

            if ("TPA_DENY".equals(payloadType)) {
                String requesterName = in.readUTF();
                String targetName = in.readUTF();
                plugin.getTeleportManager().receiveCrossServerTpaDenied(requesterName, targetName);
                return;
            }

            if ("TPA_EXPIRED".equals(payloadType)) {
                String requesterName = in.readUTF();
                String targetName = in.readUTF();
                plugin.getTeleportManager().receiveCrossServerTpaExpired(requesterName, targetName);
                return;
            }

            // Backward compatibility with old payload format
            String targetName = payloadType;
            String world = in.readUTF();
            double x = in.readDouble(), y = in.readDouble(), z = in.readDouble();
            float yaw = in.readFloat(), pitch = in.readFloat();
            plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
                Player target = plugin.getServer().getPlayerExact(targetName);
                if (target == null || !target.isOnline()) return;
                de.teleportsuite.models.TeleportLocation loc =
                    new de.teleportsuite.models.TeleportLocation(world, x, y, z, yaw, pitch,
                        plugin.getConfigManager().getServerName());
                plugin.getTeleportManager().teleport(target, loc, false);
            }, 20L);
        } catch (IOException e) {
            plugin.getLogger().warning("Fehler beim Lesen der TS-Nachricht: " + e.getMessage());
        }
    }

    @FunctionalInterface
    private interface IOConsumer<T> {
        void accept(T value) throws IOException;
    }
}
