import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Bagian 4: harness pengujian model performa — PASS/FAIL tanpa pustaka
 * eksternal. Keluar dengan exit code 1 bila ada kegagalan.
 */
public class UjiPipeline {

    private static int lulus = 0;
    private static int gagal = 0;

    public static void main(String[] args) {
        HasilPipeline virtual = jalankanVirtual();

        ujiProfilNyata(virtual);
        ujiKemacetanDanThroughput(virtual);
        ujiSimulasiParalel(virtual);
        ujiBatching();
        ujiDeterminisme(virtual);

        System.out.println();
        System.out.println("Ringkasan: " + lulus + " lulus, " + gagal + " gagal");
        if (gagal > 0) {
            System.exit(1); // exit code memberi sinyal ke proses pemanggil
        }
    }

    /** Satu pernyataan uji: catat PASS bila kondisi benar, FAIL bila salah. */
    private static void cek(String nama, boolean kondisi) {
        if (kondisi) {
            lulus++;
            System.out.println("PASS: " + nama);
        } else {
            gagal++;
            System.out.println("FAIL: " + nama);
        }
    }

    /** Menjalankan pipeline pada virtual thread per baris. */
    private static HasilPipeline jalankanVirtual() {
        try (ExecutorService eksekutor = Executors.newVirtualThreadPerTaskExecutor()) {
            return Pipeline.jalankanDenganRetry(DataContoh.isiBerkas(),
                    eksekutor, KebijakanRetry.baku());
        }
    }

    /** Statistik pipeline nyata menjadi dasar profil; angkanya pasti. */
    private static void ujiProfilNyata(HasilPipeline hasil) {
        List<BarisProfil> profil = ProfilPipeline.profil(hasil);
        cek("pipeline memproses 36 baris", hasil.jumlahBaris() == 36);
        cek("28 baris sah, 4 ditolak validasi, 4 gagal tetap",
                hasil.jumlahSah() == 28 && hasil.jumlahDitolak() == 4
                        && hasil.jumlahGagalTetap() == 4);
        cek("transformasi menanggung 38 percobaan (32 event, 6 percobaan ulang)",
                hasil.jumlahPercobaan() == 38);
        cek("backoff menunggu total 160 unit", hasil.unitTungguTotal() == 160);
        cek("tahap tulis memroses 26 baris laporan (18 utama + 8 galat)",
                hasil.laporan().size() == 18 && hasil.laporanGalat().size() == 8);
        cek("total biaya model sekuensial 900 unit",
                ProfilPipeline.totalBiaya(profil) == 900);
    }

    /** Kemacetan diidentifikasi dari angka model, bukan tebakan. */
    private static void ujiKemacetanDanThroughput(HasilPipeline hasil) {
        List<BarisProfil> profil = ProfilPipeline.profil(hasil);
        BarisProfil macet = ProfilPipeline.kemacetan(profil);
        cek("kemacetan berada di transformasi (456 unit)",
                macet.tahap() == TahapPipeline.TRANSFORMASI && macet.total() == 456);
        cek("throughput sekuensial tepat 4.00 baris per 100 unit",
                100.0 * hasil.jumlahBaris() / ProfilPipeline.waktuDinding(profil, 1) == 4.0);
        cek("throughput lebar 4 melampaui throughput sekuensial",
                ProfilPipeline.throughput(hasil.jumlahBaris(),
                        ProfilPipeline.waktuDinding(profil, 4))
                        > ProfilPipeline.throughput(hasil.jumlahBaris(),
                        ProfilPipeline.waktuDinding(profil, 1)));
    }

    /** Simulasi waktu dinding: monoton menurun, dibatasi batas Amdahl. */
    private static void ujiSimulasiParalel(HasilPipeline hasil) {
        List<BarisProfil> profil = ProfilPipeline.profil(hasil);
        long total = ProfilPipeline.totalBiaya(profil);
        long sekuensial = ProfilPipeline.biayaTahapSekuensial(profil);
        cek("lebar 1 sama dengan total sekuensial (900 unit)",
                ProfilPipeline.waktuDinding(profil, 1) == total);
        cek("lebar 4 menghasilkan 423 unit waktu dinding",
                ProfilPipeline.waktuDinding(profil, 4) == 423);
        boolean monoton = true;
        long sebelumnya = Long.MAX_VALUE;
        for (int lebar : new int[] {1, 4, 12, 36}) {
            long waktu = ProfilPipeline.waktuDinding(profil, lebar);
            if (waktu > sebelumnya || waktu < sekuensial) {
                monoton = false;
            }
            sebelumnya = waktu;
        }
        cek("waktu dinding menurun monoton dan tak pernah di bawah batas Amdahl",
                monoton);
        cek("speedup lebar 36 masih di bawah batas Amdahl",
                (double) total / ProfilPipeline.waktuDinding(profil, 36)
                        < (double) total / sekuensial);
    }

    /** Batching menurunkan jumlah task penjadwalan secara tepat. */
    private static void ujiBatching() {
        cek("penjadwalan per baris memakan 72 unit",
                ProfilPipeline.biayaPenjadwalanPerBaris(36) == 72);
        cek("penjadwalan batch 6 memakan 12 unit",
                ProfilPipeline.biayaPenjadwalanPerBatch(36, 6) == 12);
    }

    /** Regresi Bagian 2: hasil dan profil identik lintas jenis executor. */
    private static void ujiDeterminisme(HasilPipeline virtual) {
        HasilPipeline pool;
        try (ExecutorService eksekutor = Executors.newFixedThreadPool(4)) {
            pool = Pipeline.jalankanDenganRetry(DataContoh.isiBerkas(),
                    eksekutor, KebijakanRetry.baku());
        }
        cek("hasil identik lintas executor (virtual vs pool tetap 4)",
                virtual.equals(pool));
        cek("profil biaya identik lintas executor",
                ProfilPipeline.totalBiaya(ProfilPipeline.profil(pool)) == 900);
    }
}
