import java.util.Random;

/** Retry dengan exponential backoff dan jitter, seluruh jeda dihitung sebagai nilai murni. */
public class RetryBerjeda {

    /** Dependensi uji: menjawab kegagalan beberapa kali sebelum akhirnya sehat. */
    static class GudangPalsu {
        private final String kegagalan;
        private int sisaKegagalan;

        GudangPalsu(String kegagalan, int sisaKegagalan) {
            this.kegagalan = kegagalan;
            this.sisaKegagalan = sisaKegagalan;
        }

        String ambil() {
            if (sisaKegagalan > 0) {
                sisaKegagalan--;
                return kegagalan;
            }
            return "200";
        }
    }

    /** Hanya kegagalan transien yang layak diulang. */
    static boolean bolehRetry(String status) {
        return status.startsWith("5") || status.equals("408") || status.equals("429");
    }

    public static void main(String[] args) {
        final long dasarMillis = 200;
        final long batasMillis = 1600;
        final int percobaanMaks = 4;

        // Seed tetap agar contoh dapat direproduksi; produksi memakai tanpa seed.
        Random jitter = new Random(42);

        System.out.println("== Skema jeda: min(dasar * 2^(n-1), batas) + jitter 0..100 ms ==");
        for (int n = 1; n <= 4; n++) {
            long eksponensial = Math.min(dasarMillis << (n - 1), batasMillis);
            long tambahan = jitter.nextInt(101);
            System.out.println("  setelah percobaan " + n + " : tunggu "
                    + (eksponensial + tambahan) + " ms  (" + eksponensial
                    + " ms backoff + " + tambahan + " ms jitter)");
        }

        System.out.println();
        System.out.println("== Gudang pulih setelah 2 kegagalan ==");
        jitter = new Random(42); // reset agar urutan jitter sama dengan skema di atas
        GudangPalsu gudang = new GudangPalsu("503", 2);
        for (int n = 1; n <= percobaanMaks; n++) {
            String jawaban = gudang.ambil();
            if (jawaban.equals("200")) {
                System.out.println("  percobaan " + n + " -> 200 : sukses, retry berhenti");
                break;
            }
            if (!bolehRetry(jawaban)) {
                System.out.println("  percobaan " + n + " -> " + jawaban
                        + " : tidak transien, berhenti tanpa retry");
                break;
            }
            if (n == percobaanMaks) {
                System.out.println("  percobaan " + n + " -> " + jawaban
                        + " : batas " + percobaanMaks + " percobaan tercapai, menyerah");
            } else {
                long eksponensial = Math.min(dasarMillis << (n - 1), batasMillis);
                long tambahan = jitter.nextInt(101);
                System.out.println("  percobaan " + n + " -> " + jawaban + " : tunggu "
                        + (eksponensial + tambahan) + " ms lalu coba lagi");
            }
        }

        System.out.println();
        System.out.println("== Permintaan keliru: retry justru memperburuk ==");
        GudangPalsu gudangSalah = new GudangPalsu("400", 5);
        String jawaban = gudangSalah.ambil();
        if (!bolehRetry(jawaban)) {
            System.out.println("  percobaan 1 -> " + jawaban
                    + " : permintaan kita yang keliru, perbaiki pemanggil");
        }
    }
}
