package me.lucko.spark.folia;

import com.google.common.collect.ImmutableSet;
import io.papermc.paper.threadedregions.RegionizedServer;
import io.papermc.paper.util.MCUtil;
import me.lucko.spark.paper.common.SparkPlatform;
import me.lucko.spark.paper.common.SparkPlugin;
import me.lucko.spark.paper.common.monitor.ping.PlayerPingProvider;
import me.lucko.spark.paper.common.monitor.tick.TickStatistics;
import me.lucko.spark.paper.common.platform.PlatformInfo;
import me.lucko.spark.paper.common.platform.serverconfig.ServerConfigProvider;
import me.lucko.spark.paper.common.platform.world.WorldInfoProvider;
import me.lucko.spark.paper.common.sampler.ThreadDumper;
import me.lucko.spark.paper.common.sampler.source.ClassSourceLookup;
import me.lucko.spark.paper.common.sampler.source.SourceMetadata;
import net.minecraft.server.MinecraftServer;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.plugin.Plugin;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;

/**
 * Neko Core's built-in, Folia-safe spark profiler integration.
 *
 * <p>Folia disables Paper's bundled {@code io.papermc.paper.SparksFly} (its {@code enable()} is
 * hard-gated behind {@code if (false)}). Rather than patch that upstream class, Neko Core ships
 * this self-contained {@link SparkPlugin} implementation (adapted from Kitin's Folia spark port,
 * GPL-3.0) entirely under {@code src/main/java}. It builds its own {@link SparkPlatform}, registers
 * the {@code /spark} command, and wires the Folia region threads as the thread dumper — so
 * {@code /spark profiler}, {@code /spark health}, {@code /spark tps} etc. work natively on Neko Core
 * without an external plugin.
 *
 * <p>Initialised once at server startup via {@link #init()}.
 */
public final class NekoSparkPlugin implements SparkPlugin {

    private static final String COMMAND_NAME = "spark";
    private static volatile NekoSparkPlugin INSTANCE;

    private final Logger logger;
    private final Server bukkitServer;
    private final MinecraftServer server;
    private final ThreadDumper gameThreadDumper;
    private SparkPlatform platform;

    private NekoSparkPlugin(Server server) {
        this.bukkitServer = server;
        this.server = ((CraftServer) server).getServer();
        this.logger = Logger.getLogger("spark");
        this.gameThreadDumper = new ThreadDumper.Regex(ImmutableSet.of("(Folia )?Region Scheduler Thread #\\d+.*"));
    }

    /** Initialise and enable spark once, registering the /spark command. Idempotent. */
    public static synchronized void init() {
        if (INSTANCE != null) return;
        try {
            Server bukkit = org.bukkit.Bukkit.getServer();
            if (bukkit == null) return;
            NekoSparkPlugin plugin = new NekoSparkPlugin(bukkit);
            plugin.platform = new SparkPlatform(plugin);
            plugin.platform.enable();
            plugin.registerCommand(bukkit);
            INSTANCE = plugin;
            plugin.logger.log(Level.INFO, "[NekoCore] Built-in spark profiler enabled - use /spark");
        } catch (Throwable t) {
            Logger.getLogger("spark").log(Level.SEVERE, "[NekoCore] Failed to enable built-in spark", t);
        }
    }

    private void registerCommand(Server server) {
        if (server.getCommandMap().getCommand(COMMAND_NAME) == null) {
            server.getCommandMap().register(COMMAND_NAME, "neko", new CommandImpl(COMMAND_NAME));
        }
    }

    void executeCommand(CommandSender sender, String[] args) {
        this.platform.executeCommand(new KitinCommandSender(sender), args);
    }

    List<String> tabComplete(CommandSender sender, String[] args) {
        return this.platform.tabCompleteCommand(new KitinCommandSender(sender), args);
    }

    // ── SparkPlugin interface ────────────────────────────────────────────────────

    public Server getServer() { return this.bukkitServer; }

    @Override public String getVersion() { return "NekoCore-builtin"; }
    @Override public Path getPluginDirectory() { return Paths.get("plugins", "spark"); }
    @Override public String getCommandName() { return COMMAND_NAME; }

    @Override
    public Stream<KitinCommandSender> getCommandSenders() {
        return Stream.concat(
                bukkitServer.getOnlinePlayers().stream(),
                Stream.of(bukkitServer.getConsoleSender())
        ).map(KitinCommandSender::new);
    }

    @Override public void executeAsync(Runnable task) { MCUtil.scheduleAsyncTask(task); }
    @Override public void executeSync(Runnable task) { RegionizedServer.getInstance().addTask(task); }

    @Override public void log(Level level, String msg) { this.logger.log(level, msg); }
    @Override public void log(Level level, String msg, Throwable throwable) { this.logger.log(level, msg, throwable); }

    @Override public ThreadDumper getDefaultThreadDumper() { return this.gameThreadDumper; }
    @Override public TickStatistics createTickStatistics() { return new KitinTickStatistics(this.bukkitServer); }
    @Override public ClassSourceLookup createClassSourceLookup() { return new KitinClassSourceLookup(); }

    @Override
    public Collection<SourceMetadata> getKnownSources() {
        return SourceMetadata.gather(
                Arrays.asList(bukkitServer.getPluginManager().getPlugins()),
                Plugin::getName,
                plugin -> plugin.getPluginMeta().getVersion(),
                plugin -> String.join(", ", plugin.getPluginMeta().getAuthors()),
                plugin -> plugin.getPluginMeta().getDescription()
        );
    }

    @Override public PlayerPingProvider createPlayerPingProvider() { return new KitinPlayerPingProvider(this.bukkitServer); }
    @Override public ServerConfigProvider createServerConfigProvider() { return new KitinServerConfigProvider(); }
    @Override public WorldInfoProvider createWorldInfoProvider() { return new KitinWorldInfoProvider(this); }
    @Override public PlatformInfo getPlatformInfo() { return new KitinPlatformInfo(this.bukkitServer); }
    @Override public void registerApi(me.lucko.spark.api.Spark api) { }

    // ── /spark command bridge ────────────────────────────────────────────────────

    public static final class CommandImpl extends Command {
        CommandImpl(String name) {
            super(name);
            this.setPermission("spark");
        }

        @Override
        public boolean execute(CommandSender sender, String commandLabel, String[] args) {
            NekoSparkPlugin p = INSTANCE;
            if (p != null) p.executeCommand(sender, args);
            return true;
        }

        @Override
        public List<String> tabComplete(CommandSender sender, String alias, String[] args) {
            NekoSparkPlugin p = INSTANCE;
            return p != null ? p.tabComplete(sender, args) : List.of();
        }
    }
}
