package io.stellarfrontier.stellarplugin.listeners

import com.google.gson.Gson
import io.stellarfrontier.stellarplugin.SocketClient
import org.bukkit.Bukkit
import org.bukkit.event.Event
import org.bukkit.event.EventPriority
import org.bukkit.event.Listener
import org.bukkit.plugin.EventExecutor
import org.bukkit.plugin.Plugin
import org.reflections.Reflections
import java.lang.reflect.Modifier
import java.time.Instant

class UniversalListener(private val plugin: Plugin, private val socket: SocketClient) : Listener {
    private val gson = Gson()
    private val registeredEvents = mutableSetOf<String>()
    
    // Configurable enabled events (allowlist)
    // Initially empty, populated by server 'config_update'
    private var enabledEvents = setOf<String>()

    init {
        // Listen for config updates
        socket.setConfigHandler { events ->
            enabledEvents = events.toSet()
            plugin.logger.info("Updated event config: ${enabledEvents.size} events enabled")
        }
        
        registerAllEvents()
        sendMetadata()
    }

    private fun registerAllEvents() {
        val reflections = Reflections("org.bukkit.event")
        val eventClasses = reflections.getSubTypesOf(Event::class.java)

        for (clazz in eventClasses) {
            if (isValidEvent(clazz)) {
                try {
                    val executor = EventExecutor { _, event ->
                        handleEvent(event)
                    }
                    
                    // We register the event dynamically
                    plugin.server.pluginManager.registerEvent(
                        clazz as Class<out Event>,
                        this,
                        EventPriority.MONITOR,
                        executor,
                        plugin
                    )
                    registeredEvents.add(clazz.name)
                } catch (e: Exception) {
                    // Ignore registration failures
                }
            }
        }
        plugin.logger.info("UniversalListener registered ${registeredEvents.size} event types")
    }

    private fun isValidEvent(clazz: Class<out Event>): Boolean {
        return !Modifier.isAbstract(clazz.modifiers) && 
               !Modifier.isInterface(clazz.modifiers)
    }

    private fun handleEvent(event: Event) {
        val eventName = event.javaClass.name // Full class name matches DB/Config
        
        // Check allowlist
        if (!enabledEvents.contains(eventName)) return

        val data = mutableMapOf<String, Any>(
            "type" to event.javaClass.simpleName,
            "package" to event.javaClass.name.substringBeforeLast('.'),
            "timestamp" to Instant.now().toString(),
            "event_data" to serializeEvent(event)
        )

        // Try to get player name if available for better logging/display
        try {
            val getPlayer = event.javaClass.getMethod("getPlayer")
            val player = getPlayer.invoke(event)
            if (player != null) {
                // It's a player, get their name
                val getName = player.javaClass.getMethod("getName")
                data["player"] = getName.invoke(player)
            }
        } catch (ignored: Exception) {}

        socket.sendSocketEvent("universal_event", gson.toJson(data))
    }

    private fun serializeEvent(event: Event): Map<String, Any?> {
        val map = mutableMapOf<String, Any?>()
        // Use reflection to get all getter methods
        event.javaClass.methods.forEach { method ->
            if (method.name.startsWith("get") && method.parameterCount == 0 && method.name != "getClass") {
                try {
                    val fieldName = method.name.substring(3).replaceFirstChar { it.lowercase() }
                     // Skip complex objects that might cause recursion or are too large
                    val value = method.invoke(event)
                    if (isSafeToSerialize(value)) {
                         map[fieldName] = value.toString() // Simplify to string for now to avoid accidental recursion
                    }
                } catch (ignored: Exception) {}
            }
        }
        return map
    }

    private fun isSafeToSerialize(obj: Any?): Boolean {
        if (obj == null) return true
        return obj is String || obj is Number || obj is Boolean || obj is Enum<*>
    }

    public fun sendMetadata() {
        // Group by package
        val params = mapOf(
            "events" to registeredEvents.toList()
        )
        socket.sendSocketEvent("event_metadata", gson.toJson(params))
    }
}
