import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class PekerjaanAsync {

    record Pembayaran(int id, String pelanggan, long jumlah) { }

    record Hasil(int id, String status, long biayaAdmin) { }

    // "Proses pembayaran" yang memakan waktu: thread bekerja 100 ms lalu
    // mengembalikan hasil yang bisa dihitung tangan.
    static Hasil proses(Pembayaran p) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return new Hasil(p.id(), "LUNAS", p.jumlah() / 1_000 + 2_500);
    }

    public static void main(String[] args) throws Exception {
        List<Pembayaran> antrean = List.of(
                new Pembayaran(1, "Budi", 850_000),
                new Pembayaran(2, "Siti", 1_200_000),
                new Pembayaran(3, "Dewi", 430_000),
                new Pembayaran(4, "Budi", 90_000),
                new Pembayaran(5, "Siti", 2_500_000),
                new Pembayaran(6, "Dewi", 675_000));

        System.out.println("== Mode berurutan: pemanggil menunggu tiap tugas ==");
        for (Pembayaran p : antrean) {
            Hasil h = proses(p);
            System.out.println("selesai: pembayaran-" + h.id()
                    + " " + p.pelanggan() + " status=" + h.status());
        }
        System.out.println("enam tuntas, pemanggil tersita enam kali");
        System.out.println();

        System.out.println("== Mode asinkron: executor 2 thread, allOf().join() ==");
        ExecutorService pool = Executors.newFixedThreadPool(2);
        try {
            List<CompletableFuture<Hasil>> futures = new ArrayList<>();
            for (Pembayaran p : antrean) {
                // Padanan method bertanda @Async: submit, jangan tunggu di sini.
                futures.add(CompletableFuture.supplyAsync(() -> proses(p), pool));
            }
            // Pemanggil menunggu SEKALI di titik pengumpulan, bukan per tugas.
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
            for (CompletableFuture<Hasil> f : futures) {
                Hasil h = f.join();
                System.out.println("hasil pembayaran-" + h.id() + ": " + h.status()
                        + " biayaAdmin=" + h.biayaAdmin());
            }
        } finally {
            pool.shutdown();
            if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
                pool.shutdownNow();
            }
        }
        System.out.println();

        System.out.println("== Rantai thenApply + thenCombine ==");
        CompletableFuture<Long> tagihan = CompletableFuture.supplyAsync(() -> 120_000L);
        CompletableFuture<Long> denganPajak =
                tagihan.thenApply(nilai -> nilai + nilai / 10);        // +10% pajak
        CompletableFuture<Long> ongkir = CompletableFuture.supplyAsync(() -> 15_000L);
        CompletableFuture<Long> total =
                denganPajak.thenCombine(ongkir, Long::sum);
        System.out.println("120000 + pajak 10% + ongkir 15000 = " + total.join());
        System.out.println();

        System.out.println("== exceptionally: fallback saat tugas gagal ==");
        CompletableFuture<String> gagal = CompletableFuture.supplyAsync(() -> {
            throw new IllegalStateException("koneksi pembayaran terputus");
        });
        String hasil = gagal
                .exceptionally(e -> "fallback: antre ulang pembayaran")
                .join();
        System.out.println(hasil);
    }
}
