package me.sucixr.kitin.network.bandwidth;

import java.util.concurrent.atomic.AtomicLong;

/**
 * Global outbound bandwidth meter for Neko Core.
 *
 * <p>Every byte written to a client socket is counted here (via a netty outbound handler placed
 * after the frame prepender, so the count reflects the actual on-wire frame size). A periodic
 * sampler ({@link #sample()}, called ~once per second) turns the running byte counter into a
 * current send rate (Mbps), tracks a peak, and keeps a session total.
 *
 * <p>Counting is a single relaxed atomic add on the hot path; sampling is lock-free. All values
 * are server-wide (sum across every connection).
 */
public final class BandwidthMeter {

    private BandwidthMeter() {}

    // Running outbound byte counter, incremented from netty IO threads.
    private static final AtomicLong OUT_BYTES = new AtomicLong();

    private static volatile boolean samplerStarted = false;

    /**
     * Start the once-per-second sampler on a daemon thread. Using a dedicated thread (rather than
     * the server tick) keeps the rate accurate even when the main thread stalls. Idempotent.
     */
    public static synchronized void startSampler() {
        if (samplerStarted) return;
        samplerStarted = true;
        lastSampleNanos = System.nanoTime();
        lastSampleBytes = OUT_BYTES.get();
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000L);
                    sample();
                } catch (InterruptedException e) {
                    return;
                } catch (Throwable ignored) {
                    // never let the sampler die on a transient error
                }
            }
        }, "NekoCore-BandwidthSampler");
        t.setDaemon(true);
        t.start();
    }

    // Sampled state (updated only by sample(), read by display/commands).
    private static volatile long lastSampleNanos = System.nanoTime();
    private static volatile long lastSampleBytes = 0L;
    private static volatile double currentMbps = 0.0;
    private static volatile double peakMbps = 0.0;
    private static volatile long totalBytes = 0L; // session cumulative

    /** Hot path: record {@code n} bytes written toward clients. Called from netty IO threads. */
    public static void recordOutbound(int n) {
        if (n > 0) OUT_BYTES.addAndGet(n);
    }

    /**
     * Sample the counter and recompute the current send rate. Intended to run roughly once per
     * second; tolerant of jitter (uses the real elapsed time between calls).
     */
    public static void sample() {
        final long nowNanos = System.nanoTime();
        final long nowBytes = OUT_BYTES.get();

        long deltaBytes = nowBytes - lastSampleBytes;
        long deltaNanos = nowNanos - lastSampleNanos;
        if (deltaBytes < 0) deltaBytes = 0; // counter never decreases, but guard wrap anyway

        double mbps = 0.0;
        if (deltaNanos > 0) {
            // bytes -> bits -> per second -> megabits (1e6 bits = 1 Mb, network convention)
            mbps = (deltaBytes * 8.0) / (deltaNanos / 1.0e9) / 1.0e6;
        }

        currentMbps = mbps;
        if (mbps > peakMbps) peakMbps = mbps;
        totalBytes = nowBytes;
        lastSampleBytes = nowBytes;
        lastSampleNanos = nowNanos;
    }

    /** Current outbound send rate across all clients, in Mbps (megabits/s). */
    public static double getCurrentMbps() { return currentMbps; }

    /** Highest observed send rate this session, in Mbps. */
    public static double getPeakMbps() { return peakMbps; }

    /** Total bytes sent to clients this session. */
    public static long getTotalBytes() { return totalBytes; }

    /** Convenience: total sent formatted as a human-readable string. */
    public static String getTotalHuman() {
        return humanBytes(totalBytes);
    }

    public static void resetPeak() { peakMbps = currentMbps; }

    public static String humanBytes(long bytes) {
        if (bytes < 1024L) return bytes + " B";
        double kb = bytes / 1024.0;
        if (kb < 1024.0) return String.format("%.1f KiB", kb);
        double mb = kb / 1024.0;
        if (mb < 1024.0) return String.format("%.2f MiB", mb);
        double gb = mb / 1024.0;
        return String.format("%.2f GiB", gb);
    }
}
