package dev.m1sk9.lunaticChat.velocity.messaging

import com.velocitypowered.api.event.Subscribe
import com.velocitypowered.api.event.connection.PluginMessageEvent
import com.velocitypowered.api.proxy.ProxyServer
import com.velocitypowered.api.proxy.ServerConnection
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier
import dev.m1sk9.lunaticChat.engine.protocol.PluginMessage
import dev.m1sk9.lunaticChat.engine.protocol.PluginMessageCodec
import dev.m1sk9.lunaticChat.engine.protocol.ProtocolVersion
import dev.m1sk9.lunaticChat.velocity.presence.PresenceTracker
import org.slf4j.Logger

/**
 * Handles plugin messages from Paper servers
 */
class PluginMessageHandler(
    /**
     * Plugin instance used for event registration.
     * Type is [Any] because Velocity's EventManager.register() accepts Object.
     * Could be made generic, but provides little practical benefit since the API itself is not type-safe.
     * Typically the main plugin instance is passed here.
     */
    private val plugin: Any,
    private val server: ProxyServer,
    private val logger: Logger,
    private val pluginVersion: String,
    private val crossServerChatRelay: CrossServerChatRelay,
    private val crossServerDirectMessageRelay: CrossServerDirectMessageRelay,
    private val presenceTracker: PresenceTracker,
) {
    companion object {
        private val CHANNEL = MinecraftChannelIdentifier.create("lunaticchat", "main")
    }

    /**
     * Initialize
     */
    fun initialize() {
        server.channelRegistrar.register(CHANNEL)
        server.eventManager.register(plugin, this)
        logger.info("Plugin message handler registered for channel: $CHANNEL")
    }

    /**
     * Plugin message event
     */
    @Subscribe
    fun onPluginMessage(event: PluginMessageEvent) {
        if (event.identifier != CHANNEL) return

        val source = event.source
        if (source !is ServerConnection) {
            logger.warn("Received plugin message from non-server source: ${source::class.simpleName}")
            return
        }

        try {
            when (val message = PluginMessageCodec.decode(event.data)) {
                is PluginMessage.Handshake -> {
                    handleHandshake(source, message)
                }
                is PluginMessage.StatusRequest -> {
                    handleStatusRequest(source)
                }
                is PluginMessage.GlobalChatMessage -> {
                    handleGlobalChatMessage(source, message)
                }
                is PluginMessage.DirectMessageRelay -> {
                    crossServerDirectMessageRelay.relay(message, source.server)
                }
                is PluginMessage.PresenceRequest -> {
                    presenceTracker.sendSnapshotTo(source.server)
                }
                else -> {
                    logger.warn("Unexpected message type from Paper: ${message::class.simpleName}")
                }
            }
        } catch (e: Exception) {
            logger.error("Failed to decode plugin message: ${e.message}", e)
        }
    }

    /**
     * Handles handshake message
     */
    private fun handleHandshake(
        connection: ServerConnection,
        handshake: PluginMessage.Handshake,
    ) {
        logger.info(
            "Received handshake from ${connection.serverInfo.name}: " +
                "Plugin=${handshake.pluginVersion}, Protocol=${handshake.protocolMajor}.${handshake.protocolMinor}.${handshake.protocolPatch}",
        )

        // Protocol version check (MAJOR must match, MINOR within supported range)
        val protocolCompatible =
            ProtocolVersion.isCompatible(
                handshake.protocolMajor,
                handshake.protocolMinor,
            )
        if (!protocolCompatible) {
            val error =
                "Protocol version incompatible: Paper=${handshake.protocolMajor}.${handshake.protocolMinor}.${handshake.protocolPatch}, " +
                    "Velocity=${ProtocolVersion.version}"
            logger.error(error)
            sendHandshakeResponse(connection, false, error)
            return
        }

        // Success
        logger.info("Handshake successful with ${connection.serverInfo.name}")
        sendHandshakeResponse(connection, true, null)
    }

    /**
     * Sends handshake response
     */
    private fun sendHandshakeResponse(
        connection: ServerConnection,
        compatible: Boolean,
        error: String?,
    ) {
        val response =
            PluginMessage.HandshakeResponse(
                compatible = compatible,
                velocityVersion = pluginVersion,
                error = error,
                protocolMajor = ProtocolVersion.MAJOR,
                protocolMinor = ProtocolVersion.MINOR,
                protocolPatch = ProtocolVersion.PATCH,
            )

        val data = PluginMessageCodec.encode(response)

        // Send
        connection.sendPluginMessage(CHANNEL, data)

        if (compatible) {
            logger.info("Sent successful handshake response to ${connection.serverInfo.name}")
        } else {
            logger.warn("Sent failed handshake response to ${connection.serverInfo.name}: $error")
        }
    }

    /**
     * Handles status request
     */
    private fun handleStatusRequest(connection: ServerConnection) {
        logger.info("Received status request from ${connection.serverInfo.name}")

        val response =
            PluginMessage.StatusResponse(
                velocityVersion = pluginVersion,
                protocolVersion = ProtocolVersion.version,
                online = true,
            )

        val data = PluginMessageCodec.encode(response)
        connection.sendPluginMessage(CHANNEL, data)

        logger.info("Sent status response to ${connection.serverInfo.name}")
    }

    /**
     * Handles global chat message and relays to other servers
     */
    private fun handleGlobalChatMessage(
        connection: ServerConnection,
        message: PluginMessage.GlobalChatMessage,
    ) {
        logger.info(
            "Received global chat message from ${connection.serverInfo.name}: " +
                "messageId=${message.messageId}, player=${message.playerName}",
        )

        crossServerChatRelay.relayGlobalMessage(message, connection.server)
    }

    /**
     * Shutdown
     */
    fun shutdown() {
        server.eventManager.unregisterListener(plugin, this)
        logger.info("Plugin message handler unregistered")
    }
}
