package custom.sextant;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

// >>> Import World from YOUR fork. Common ones:
//       L2jMobius:            org.l2jmobius.gameserver.model.World
//       L2J Server / Unity:   com.l2jserver.gameserver.model.world.L2World  (count: L2World.getInstance().getAllPlayers().size())
//       aCis:                 net.sf.l2j.gameserver.model.World             (count: World.getInstance().getPlayers().size())
//       L2jFrozen / older:    com.l2jfrozen.gameserver.model.world.L2World
import org.l2jmobius.gameserver.model.World;

/**
 * Sextant module — reports this server's REAL online count to sextant.gg every
 * five minutes, signed with your server's secret. Sextant cross-checks it
 * against the count it reads from outside; when the two agree your server
 * reaches FIXED, the highest automatic class. No player data leaves the server —
 * only a single integer, the number online right now.
 *
 * WHO THIS IS FOR: the whole L2J family — L2J, L2jMobius, L2jUnity, aCis,
 * L2jFrozen and derivatives (any Java gameserver). L2OFF / C++ (official-files)
 * servers cannot run a Java datapack script — use sextant-report.sh instead
 * (it reads your count from a DB query and signs the same HTTP report), or make
 * the signed POST from your own code. The report endpoint is identical for all.
 *
 * INSTALL (L2J family)
 *   1. Put this file in  data/scripts/custom/sextant/SextantReporter.java
 *   2. Fill in SERVER_REF and API_SECRET below. Get the secret from your
 *      server's page on sextant.gg: Account -> your server -> module secret.
 *   3. Call SextantReporter.start() once, after the game server has loaded
 *      (e.g. at the end of GameServer's constructor), then restart.
 *
 * onlineCount() is the ONLY fork-specific line — change it (and the World import
 * above) to match how your build exposes the online player count.
 */
public final class SextantReporter
{
    // ---- fill these in ----------------------------------------------------
    private static final String SERVER_REF = "YOUR-SERVER-SLUG"; // e.g. nolifer-bf2b
    private static final String API_SECRET = "YOUR-API-SECRET";
    // -----------------------------------------------------------------------

    private static final String ENDPOINT = "https://sextant.gg/api/module/report";
    private static final long PERIOD_MINUTES = 5;

    private static final HttpClient HTTP = HttpClient.newHttpClient();
    private static ScheduledExecutorService SCHEDULER;

    private SextantReporter()
    {
    }

    /** Call once after the game server has loaded. Safe to call twice (no-op). */
    public static synchronized void start()
    {
        if (SCHEDULER != null)
        {
            return;
        }
        SCHEDULER = Executors.newSingleThreadScheduledExecutor(r ->
        {
            final Thread t = new Thread(r, "sextant-reporter");
            t.setDaemon(true);
            return t;
        });
        SCHEDULER.scheduleAtFixedRate(SextantReporter::report, 1, PERIOD_MINUTES, TimeUnit.MINUTES);
    }

    private static int onlineCount()
    {
        // >>> the only fork-specific line — return your real online player count.
        return World.getInstance().getPlayers().size();
    }

    private static void report()
    {
        try
        {
            final String body = "{\"players\":" + onlineCount() + "}";
            final String ts = Long.toString(System.currentTimeMillis());
            final String signature = hmacSha256(ts + "." + body, API_SECRET);
            final HttpRequest req = HttpRequest.newBuilder(URI.create(ENDPOINT))
                .header("content-type", "application/json")
                .header("x-sextant-server", SERVER_REF)
                .header("x-sextant-timestamp", ts)
                .header("x-sextant-signature", signature)
                .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
                .build();
            // Fire-and-forget: reporting must never stall or affect the game loop.
            HTTP.sendAsync(req, HttpResponse.BodyHandlers.discarding());
        }
        catch (Exception e)
        {
            // Swallow — try again next cycle. A monitoring hiccup is not a game bug.
        }
    }

    private static String hmacSha256(String data, String secret) throws Exception
    {
        final Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        final byte[] raw = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        final StringBuilder hex = new StringBuilder(raw.length * 2);
        for (byte b : raw)
        {
            hex.append(Character.forDigit((b >> 4) & 0xF, 16));
            hex.append(Character.forDigit(b & 0xF, 16));
        }
        return hex.toString();
    }
}
