package de.teleportsuite.bungeemessaging;

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

import java.io.*;

/**
 * Spigot-seitiger Kanal zum TeleportSuite-Bungee-Plugin.
 *
 * Prinzip (identisch zu BTM):
 *   Spigot schickt Anfragen auf CHANNEL_TO_BUNGEE.
 *   BungeeCord erledigt connect() + routing und schickt Befehle auf CHANNEL_TO_SPIGOT.
 *   Spigot führt den finalen player.teleport() aus.
 *
 * Payloads Spigot → Bungee (CHANNEL_TO_BUNGEE):
 *   TP_PLAYER          senderName, targetName
 *   TP_PLAYER_TO_PLAYER requesterName, fromName, toName
 *   TP_HERE            senderName, targetName
 *   TP_ALL             senderName
 *   TP_POS             playerName, server, world, x, y, z, yaw, pitch
 *   TPA_REQUEST        fromName, toName, type, msgToTarget, msgToRequester
 *   TPA_ACCEPT         toName, fromName, errMsg
 *   TPA_DENY           toName, fromName, msg
 *   TPA_CANCEL         fromName, msgFrom, msgTo
 *   TPA_EXPIRED        fromName, toName, msgFrom, msgTo
 *   SEND_MSG           targetName, message
 *
 * Payloads Bungee → Spigot (CHANNEL_TO_SPIGOT):
 *   TP_PLAYERTOPLAYER  senderName, targetName
 *   TP_PLAYERTOPOSITION playerName, world, x, y, z, yaw, pitch
 *   SEND_MSG           playerName, message
 */
public class BungeeMessenger implements PluginMessageListener {

    public static final String CHANNEL_TO_BUNGEE = "teleportsuite:tobungee";
    public static final String CHANNEL_TO_SPIGOT  = "teleportsuite:tospigot";

    private final TeleportSuite plugin;

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

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

    public void unregister() {
        plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, CHANNEL_TO_BUNGEE);
        plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, CHANNEL_TO_SPIGOT, this);
    }

    // -------------------------------------------------------------------------
    // Outgoing helpers (Spigot → Bungee)
    // -------------------------------------------------------------------------

    /** /tp <target> – Absender teleportiert sich zum Zielspieler */
    public void teleportToPlayer(Player sender, String targetName) {
        send(sender, out -> {
            out.writeUTF("TP_PLAYER");
            out.writeUTF(sender.getName());
            out.writeUTF(targetName);
        });
    }

    /** /tp <from> <to> – Admin teleportiert einen Spieler zu einem anderen */
    public void teleportPlayerToPlayer(Player requester, String fromName, String targetName) {
        send(requester, out -> {
            out.writeUTF("TP_PLAYER_TO_PLAYER");
            out.writeUTF(requester.getName());
            out.writeUTF(fromName);
            out.writeUTF(targetName);
        });
    }

    /** /tphere <target> – Zielspieler wird zu Absender geholt */
    public void teleportHere(Player sender, String targetName) {
        send(sender, out -> {
            out.writeUTF("TP_HERE");
            out.writeUTF(sender.getName());
            out.writeUTF(targetName);
        });
    }

    /** /tpall – Alle Netzwerkspieler zum Absender teleportieren */
    public void teleportAll(Player sender) {
        send(sender, out -> {
            out.writeUTF("TP_ALL");
            out.writeUTF(sender.getName());
        });
    }

    /**
     * Home / Warp / Spawn / Back / SavePoint / Portal / TpPos:
     * Koordinaten sind bekannt → Bungee wechselt den Server und Spigot
     * setzt den Spieler direkt an die exakten Koordinaten.
     */
    public void teleportToPosition(Player player, String server, String world,
                                   double x, double y, double z, float yaw, float pitch) {
        send(player, out -> {
            out.writeUTF("TP_POS");
            out.writeUTF(player.getName());
            out.writeUTF(server);
            out.writeUTF(world);
            out.writeDouble(x);
            out.writeDouble(y);
            out.writeDouble(z);
            out.writeFloat(yaw);
            out.writeFloat(pitch);
        });
    }

    // TPA

    public void sendTpaRequest(Player from, String toName,
                               String type, String msgToTarget, String msgToRequester) {
        send(from, out -> {
            out.writeUTF("TPA_REQUEST");
            out.writeUTF(from.getName());
            out.writeUTF(toName);
            out.writeUTF(type);
            out.writeUTF(msgToTarget);
            out.writeUTF(msgToRequester);
        });
    }

    public void sendTpaAccept(Player acceptor, String fromName, String errMsg) {
        send(acceptor, out -> {
            out.writeUTF("TPA_ACCEPT");
            out.writeUTF(acceptor.getName());
            out.writeUTF(fromName == null ? "nu" : fromName);
            out.writeUTF(errMsg);
        });
    }

    public void sendTpaDeny(Player denier, String fromName, String msg) {
        send(denier, out -> {
            out.writeUTF("TPA_DENY");
            out.writeUTF(denier.getName());
            out.writeUTF(fromName == null ? "nu" : fromName);
            out.writeUTF(msg);
        });
    }

    public void sendTpaCancel(Player canceller, String msgFrom, String msgTo) {
        send(canceller, out -> {
            out.writeUTF("TPA_CANCEL");
            out.writeUTF(canceller.getName());
            out.writeUTF(msgFrom);
            out.writeUTF(msgTo);
        });
    }

    public void sendTpaExpired(Player from, String toName, String msgFrom, String msgTo) {
        send(from, out -> {
            out.writeUTF("TPA_EXPIRED");
            out.writeUTF(from.getName());
            out.writeUTF(toName);
            out.writeUTF(msgFrom);
            out.writeUTF(msgTo);
        });
    }

    // -------------------------------------------------------------------------
    // Incoming handler (Bungee → Spigot)
    // -------------------------------------------------------------------------

    @Override
    public void onPluginMessageReceived(String channel, Player unused, byte[] bytes) {
        if (!channel.equals(CHANNEL_TO_SPIGOT)) return;

        try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(bytes))) {
            String task = in.readUTF();

            switch (task) {

                // Bungee hat sender bereits auf diesen Server gewechselt.
                // Sobald beide Spieler lokal online sind, teleportieren.
                case "TP_PLAYERTOPLAYER": {
                    String senderName = in.readUTF();
                    String targetName = in.readUTF();
                    schedulePlayerToPlayerTeleportWithBack(senderName, targetName);
                    break;
                }

                // Bungee hat player bereits auf diesen Server gewechselt.
                // Nach kurzem Delay an exakte Koordinaten teleportieren.
                case "TP_PLAYERTOPOSITION": {
                    String playerName = 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, () -> {
                        org.bukkit.entity.Player p = plugin.getServer().getPlayerExact(playerName);
                        if (p == null || !p.isOnline()) return;
                        // Save back location BEFORE teleporting (teleport() with saveBack=false
                        // skips this, so we do it manually here for cross-server teleports)
                        plugin.getDatabaseManager().saveLastLocation(p.getUniqueId(),
                                new TeleportLocation(p.getLocation(), plugin.getConfigManager().getServerName()));
                        TeleportLocation dest = new TeleportLocation(
                                world, x, y, z, yaw, pitch,
                                plugin.getConfigManager().getServerName());
                        plugin.getTeleportManager().teleport(p, dest, false);
                    }, 10L);
                    break;
                }

                // Direkte Chat-Nachricht an einen lokalen Spieler
                case "SEND_MSG": {
                    String playerName = in.readUTF();
                    String message    = in.readUTF();
                    plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
                        org.bukkit.entity.Player p = plugin.getServer().getPlayerExact(playerName);
                        if (p != null && p.isOnline()) p.sendMessage(message);
                    }, 1L);
                    break;
                }

                default:
                    plugin.getLogger().warning("Unbekannter Bungee-Task: " + task);
            }
        } catch (IOException e) {
            plugin.getLogger().warning("Fehler beim Lesen der Bungee-Nachricht: " + e.getMessage());
        }
    }

    // -------------------------------------------------------------------------
    // Internal helpers
    // -------------------------------------------------------------------------

    /**
     * Wartet bis sender UND target lokal online sind (max. 100 × 10 Ticks = 50 s),
     * speichert Back-Location und führt dann player.teleport() aus.
     */
    private void schedulePlayerToPlayerTeleportWithBack(String senderName, String targetName) {
        final int[] attempts = {0};
        final int[] taskId   = {-1};

        taskId[0] = plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, () -> {
            attempts[0]++;
            org.bukkit.entity.Player sender = plugin.getServer().getPlayerExact(senderName);
            org.bukkit.entity.Player target = plugin.getServer().getPlayerExact(targetName);

            if (sender != null && sender.isOnline() && target != null && target.isOnline()) {
                // Save back location before teleport so /back works cross-server
                plugin.getDatabaseManager().saveLastLocation(sender.getUniqueId(),
                        new TeleportLocation(sender.getLocation(),
                                plugin.getConfigManager().getServerName()));
                plugin.getTeleportManager().teleport(
                        sender,
                        new TeleportLocation(target.getLocation(),
                                plugin.getConfigManager().getServerName()),
                        false);
                plugin.getServer().getScheduler().cancelTask(taskId[0]);
                return;
            }

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

    @FunctionalInterface
    private interface IOWriter {
        void write(DataOutputStream out) throws IOException;
    }

    /** Schreibt ein Payload und schickt es über den Bungee-Kanal. */
    private void send(Player anchor, IOWriter writer) {
        if (anchor == null || !anchor.isOnline()) return;
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             DataOutputStream out = new DataOutputStream(bos)) {
            writer.write(out);
            anchor.sendPluginMessage(plugin, CHANNEL_TO_BUNGEE, bos.toByteArray());
        } catch (IOException e) {
            plugin.getLogger().warning("Bungee-Send Fehler: " + e.getMessage());
        }
    }
}
