package org.server_info.server_info

import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
import org.bukkit.command.TabCompleter
import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.scheduler.BukkitRunnable
import java.lang.management.ManagementFactory
import java.text.DecimalFormat
import com.sun.management.OperatingSystemMXBean

class Server_info : JavaPlugin() {

    private var lastTickTime = System.currentTimeMillis()
    private var tickCount = 0
    private val tickTimeList = ArrayList<Long>()
    private var tps = 20.0
    private val df = DecimalFormat("#.##")

    private var serverTitle = "Paper"
    private var cpuUsage = 0.0
    private var lastCpuTime = 0L
    private var lastSampleTime = 0L

    override fun onEnable() {
        saveDefaultConfig()
        loadConfiguration()

        getCommand("serverinfo")?.let { command ->
            val commandExecutor = ServerInfoCommand(this)
            command.setExecutor(commandExecutor)
            command.tabCompleter = commandExecutor
        }

        startTPSCalculation()
        startCPUMonitor()
        startTabListUpdater()

        logger.info("ServerInfo plugin has been enabled!")
    }

    override fun onDisable() {
        logger.info("ServerInfo plugin has been disabled!")
        saveConfig()
    }

    private fun loadConfiguration() {
        serverTitle = config.getString("server-title") ?: "Paper"
        if (!config.contains("server-title")) {
            config.set("server-title", serverTitle)
            saveConfig()
        }
    }

    fun setServerTitle(title: String) {
        serverTitle = title
        config.set("server-title", title)
        saveConfig()
        updateTabList()
    }

    private fun startTPSCalculation() {
        object : BukkitRunnable() {
            override fun run() {
                tickCount++

                val now = System.currentTimeMillis()
                val elapsed = now - lastTickTime
                lastTickTime = now

                if (tickCount > 1) {
                    tickTimeList.add(elapsed)
                }

                while (tickTimeList.size > 600) {
                    tickTimeList.removeAt(0)
                }

                if (tickTimeList.isNotEmpty()) {
                    val avgTickTime = tickTimeList.average()
                    tps = 1000.0 / avgTickTime
                    tps = tps.coerceIn(0.0, 20.0)
                }
            }
        }.runTaskTimer(this, 1L, 1L)
    }

    private fun startCPUMonitor() {
        object : BukkitRunnable() {
            override fun run() {
                updateCPUUsage()
            }
        }.runTaskTimer(this, 20L, 20L) // Update every second
    }

    private fun updateCPUUsage() {
        try {
            val osBean = ManagementFactory.getOperatingSystemMXBean()

            if (osBean is com.sun.management.OperatingSystemMXBean) {
                // Modern JVM with com.sun.management API
                cpuUsage = osBean.processCpuLoad * 100
            } else {
                // Fallback to more basic metrics
                val runtime = Runtime.getRuntime()
                val processors = runtime.availableProcessors()

                // Get JVM CPU time if available
                val bean = ManagementFactory.getThreadMXBean()
                if (bean.isThreadCpuTimeSupported && bean.isThreadCpuTimeEnabled) {
                    var totalCpuTime = 0L
                    for (threadId in bean.allThreadIds) {
                        val cpuTime = bean.getThreadCpuTime(threadId)
                        if (cpuTime >= 0) totalCpuTime += cpuTime
                    }

                    val now = System.currentTimeMillis()

                    if (lastCpuTime > 0L && lastSampleTime > 0L) {
                        val elapsedCpu = totalCpuTime - lastCpuTime
                        val elapsedTime = (now - lastSampleTime) * 1000000 // Convert to nanoseconds

                        if (elapsedTime > 0) {
                            // Calculate CPU usage as a percentage across all cores
                            cpuUsage = (elapsedCpu * 100.0) / (elapsedTime * processors)
                            cpuUsage = cpuUsage.coerceIn(0.0, 100.0)
                        }
                    }

                    lastCpuTime = totalCpuTime
                    lastSampleTime = now
                } else {
                    // If CPU time not supported, use a basic estimate ( If this happens please check the server for issues! )
                    val threadCount = ManagementFactory.getThreadMXBean().threadCount
                    logger.warning("§4Accurate CPU Monitoring isn't supported! Check Server for Issues!")
                    cpuUsage = (threadCount * 100.0) / (processors * 20.0)
                    cpuUsage = cpuUsage.coerceIn(0.0, 100.0)
                }
            }
        } catch (e: Exception) {
            logger.warning("Failed to calculate CPU usage: ${e.message}")
            cpuUsage = 0.0
        }
    }

    private fun startTabListUpdater() {
        object : BukkitRunnable() {
            override fun run() {
                updateTabList()
            }
        }.runTaskTimer(this, 20L, 20L)
    }

    private fun updateTabList() {
        val header = getTabListHeader()
        val footer = getTabListFooter()

        for (player in Bukkit.getOnlinePlayers()) {
            player.sendPlayerListHeaderAndFooter(header, footer)
        }
    }

    private fun getTabListHeader(): Component {
        return Component.text("§6§l$serverTitle\n§r§7Online players: §f${server.onlinePlayers.size} / §d${server.maxPlayers}")
    }

    private fun getTabListFooter(): Component {
        val runtime = Runtime.getRuntime()
        val maxMemory = runtime.maxMemory()
        val totalMemory = runtime.totalMemory()
        val freeMemory = runtime.freeMemory()
        val usedMemory = totalMemory - freeMemory

        val tpsColor = when {
            tps >= 18.0 -> "§2"
            tps >= 15.0 -> "§e"
            else -> "§c"
        }

        return Component.text(
            "\n§7RAM: §f${formatMemory(usedMemory)} / ${formatMemory(maxMemory)}" +
                    "\n§7CPU: ${formatCPU(cpuUsage)}%" +
                    "\n§7TPS: $tpsColor${df.format(tps)}"
        )
    }

    fun getServerInfo(): String {
        val runtime = Runtime.getRuntime()
        val maxMemory = runtime.maxMemory()
        val totalMemory = runtime.totalMemory()
        val freeMemory = runtime.freeMemory()
        val usedMemory = totalMemory - freeMemory
        val jvmFreeMemory = maxMemory - usedMemory

        return buildString {
            append("§6--- ServerInfo ---\n")
            append("§6Title: §f$serverTitle\n")
            append("§aRAM Usage: §f${formatMemory(usedMemory)} / ${formatMemory(maxMemory)}\n")
            append("§aJVM Free: §f${formatMemory(jvmFreeMemory)}\n")
            append("§aJVM Allocated: §f${formatMemory(totalMemory)}\n")
            append("§aMax RAM: §f${formatMemory(maxMemory)}\n")
            append("§aCPU Load: ${formatCPU(cpuUsage)}%\n")
            append("§aTPS: ${formatTPS(tps)}\n")
        }
    }

    private fun formatTPS(tps: Double): String {
        return when {
            tps >= 18.0 -> "§2${df.format(tps)}"
            tps >= 15.0 -> "§e${df.format(tps)}"
            else -> "§c${df.format(tps)}"
        }
    }

    private fun formatMemory(valueInBytes: Long): String {
        val mbValue = valueInBytes / (1024 * 1024)
        return if (mbValue >= 1024) {
            val gbValue = mbValue / 1024.0
            "${df.format(gbValue)} GB"
        } else {
            "$mbValue MB"
        }
    }

    private fun formatCPU(cpu: Double): String {
        return when {
            cpu <= 30.0 -> "§2${df.format(cpu)}"
            cpu <= 70.0 -> "§e${df.format(cpu)}"
            else -> "§c${df.format(cpu)}"
        }
    }

    fun getPingForPlayer(player: Player): Int {
        return try {
            val entityPlayer = player.javaClass.getMethod("getHandle").invoke(player)
            val pingField = entityPlayer.javaClass.getField("ping")
            pingField.getInt(entityPlayer)
        } catch (e: Exception) {
            logger.warning("Failed to get ping for player: ${e.message}")
            -1
        }
    }

    // Create CommandClass that stores the Commands that can be used
    private inner class ServerInfoCommand(private val plugin: Server_info) : CommandExecutor, TabCompleter {

        override fun onCommand(sender: CommandSender, cmd: Command, label: String, args: Array<out String>): Boolean {
            if (cmd.name.equals("serverinfo", ignoreCase = true)) {
                if (args.isNotEmpty()) {
                    when (args[0].lowercase()) {
                        "title", "settitle" -> {
                            if (!sender.hasPermission("serverinfo.admin")) {
                                sender.sendMessage("§cYou don't have permission to change the server title.")
                                return true
                            }

                            if (args.size < 2) {
                                sender.sendMessage("§cUsage: /serverinfo settitle <title>")
                                return true
                            }

                            val newTitle = args.copyOfRange(1, args.size).joinToString(" ")
                            plugin.setServerTitle(newTitle)
                            sender.sendMessage("§aServer title changed to: §f$newTitle")
                            return true
                        }
                        "reload" -> {
                            if (!sender.hasPermission("serverinfo.admin")) {
                                sender.sendMessage("§cYou don't have permission to reload the plugin.")
                                return true
                            }

                            plugin.reloadConfig()
                            plugin.loadConfiguration()
                            sender.sendMessage("§aServerInfo plugin configuration reloaded.")
                            return true
                        }
                    }
                }

                sender.sendMessage(plugin.getServerInfo())

                if (sender is Player) {
                    val ping = plugin.getPingForPlayer(sender)
                    sender.sendMessage("§aYour Ping: §f${ping}ms")
                    sender.sendMessage("§6-- Powered by Kotlin --")
                }

                return true
            }
            return false
        }

        override fun onTabComplete(sender: CommandSender, command: Command, alias: String, args: Array<out String>): List<String>? {
            if (command.name.equals("serverinfo", ignoreCase = true)) {
                if (args.size == 1) {
                    val completions = mutableListOf<String>()
                    if (sender.hasPermission("serverinfo.admin")) {
                        completions.add("settitle")
                        completions.add("reload")
                    }
                    return completions.filter { it.startsWith(args[0].lowercase()) }
                }
            }
            return null
        }
    }
}