import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/** Logging mini: filter level, log terstruktur JSON dengan MDC, dan rotasi berkas. */
public class LogTerstruktur {

    enum Level { TRACE, DEBUG, INFO, WARN, ERROR }

    /** Satu peristiwa log: waktu beku, level, logger, pesan, dan konteks MDC. */
    record Peristiwa(LocalDateTime waktu, Level level, String logger,
                     String requestId, String pesan) {}

    static final Level AMBANG = Level.INFO;
    static final DateTimeFormatter FORMAT =
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");

    /** MDC ala SLF4J: konteks per thread yang ikut setiap baris log. */
    static final ThreadLocal<String> MDC_REQUEST_ID = new ThreadLocal<>();

    /** Lolos saringan ambang: level >= AMBANG. */
    static boolean lolos(Level level) {
        return level.ordinal() >= AMBANG.ordinal();
    }

    /** Bentuk baris JSON — pengganti sederhana encoder JSON milik Logback. */
    static String json(Peristiwa p) {
        String pesanAman = p.pesan().replace("\\", "\\\\").replace("\"", "\\\"");
        String mdc = (p.requestId() == null) ? "{}" : "{\"requestId\":\"" + p.requestId() + "\"}";
        return "{\"ts\":\"" + FORMAT.format(p.waktu()) + "\",\"level\":\"" + p.level()
                + "\",\"logger\":\"" + p.logger() + "\",\"mdc\":" + mdc
                + ",\"msg\":\"" + pesanAman + "\"}";
    }

    public static void main(String[] args) {
        LocalDateTime dasar = LocalDateTime.of(2026, 1, 15, 8, 0, 0);
        List<Peristiwa> semuanya = List.of(
                new Peristiwa(dasar.plusSeconds(1), Level.DEBUG, "db.pool", null,
                        "koneksi dibuka"),
                new Peristiwa(dasar.plusSeconds(1), Level.INFO, "server", null,
                        "aplikasi menyala"),
                new Peristiwa(dasar.plusSeconds(2), Level.WARN, "db.pool", null,
                        "koneksi ulang setelah timeout"),
                new Peristiwa(dasar.plusSeconds(3), Level.ERROR, "api", "req-77",
                        "gagal memproses pesanan \"P-1042\""));

        System.out.println("== Saringan level (ambang " + AMBANG + ") ==");
        for (Peristiwa p : semuanya) {
            if (lolos(p.level())) {
                System.out.printf("[%s] %s - %s%n", String.format("%-5s", p.level()),
                        p.logger(), p.pesan());
            } else {
                System.out.printf("[%s] %s - %s  (DILEWATI: di bawah ambang)%n",
                        String.format("%-5s", p.level()), p.logger(), p.pesan());
            }
        }
        System.out.println();

        System.out.println("== Bentuk terstruktur (JSON) untuk agregator ==");
        for (Peristiwa p : semuanya) {
            if (lolos(p.level())) {
                System.out.println(json(p));
            }
        }
        System.out.println();

        System.out.println("== Rotasi berkas: batas 400 byte ==");
        List<String> baris = new ArrayList<>();
        for (Peristiwa p : semuanya) {
            if (lolos(p.level())) {
                baris.add(json(p));
            }
        }
        baris.addAll(List.of(
                "{\"ts\":\"2026-01-15T08:05:00\",\"level\":\"INFO\",\"logger\":\"api\",\"mdc\":{},\"msg\":\"GET /api/pesanan 200\"}",
                "{\"ts\":\"2026-01-15T08:05:01\",\"level\":\"INFO\",\"logger\":\"api\",\"mdc\":{},\"msg\":\"GET /api/pesanan 200\"}"));
        int batas = 400;
        int nomorBerkas = 0;
        long terisi = 0;
        int[] perBerkas = new int[3];
        for (String b : baris) {
            if (terisi + b.getBytes().length > batas && nomorBerkas < 2) {
                nomorBerkas++;
                terisi = 0;
            }
            terisi += b.getBytes().length + 1; // +1 untuk baris baru
            perBerkas[nomorBerkas]++;
        }
        String[] nama = {"app.log", "app.log.1", "app.log.2"};
        for (int i = 0; i <= nomorBerkas; i++) {
            System.out.printf("  %-12s %d baris%n", nama[i], perBerkas[i]);
        }
    }
}
