import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;

// Harness pengujian berbasis main: tanpa pustaka uji, cukup JDK murni.
// Setiap periksa() mencetak PASS/FAIL; exit code bukan nol bila ada kegagalan.
public class UjiPerpustakaan {

    private static final String ISBN_LASKAR = "978-602-421-123-4";
    private static final String ISBN_FILOSOFI = "978-602-401-718-3";
    private static final String ISBN_LAUT = "978-602-291-024-4";
    private static final String ISBN_BUMI = "978-979-433-555-1";

    private static int gagal = 0;

    public static void main(String[] args) throws PersistensiGagalException {
        ujiKatalog();
        ujiSirkulasi();
        ujiDenda();
        ujiPersistensi();

        System.out.println();
        System.out.println(gagal == 0 ? "SEMUA UJI LULUS" : gagal + " UJI GAGAL");
        System.exit(gagal == 0 ? 0 : 1);
    }

    static void ujiKatalog() {
        System.out.println("== Katalog ==");
        Perpustakaan perpus = perpusBaru();
        periksa("buku duplikat ditolak", melempar(() -> perpus.tambahBuku(
                new Buku(ISBN_LASKAR, "Laskar Pelangi", "Andrea Hirata", 2005)),
                PerpustakaanException.class));
        periksa("anggota duplikat ditolak", melempar(() -> perpus.daftarkan(
                new Anggota("A-001", "Budi Santoso", "budi@example.com",
                        LocalDate.of(2026, 1, 2))),
                PerpustakaanException.class));
        periksa("pencarian mengabaikan huruf besar", perpus.cariBuku("LASKAR").size() == 1);
        periksa("pencarian tanpa hasil menghasilkan list kosong",
                perpus.cariBuku("java").isEmpty());
    }

    static void ujiSirkulasi() {
        System.out.println("== Sirkulasi ==");
        Perpustakaan perpus = perpusBaru();
        periksa("ISBN tidak dikenal ditolak", melempar(
                () -> perpus.pinjam("978-000-000-000-0", "A-001", LocalDate.of(2026, 1, 5)),
                BukuTidakDitemukanException.class));
        periksa("anggota tidak terdaftar ditolak", melempar(
                () -> perpus.pinjam(ISBN_LASKAR, "A-099", LocalDate.of(2026, 1, 5)),
                AnggotaTidakDitemukanException.class));

        perpus.pinjam(ISBN_LASKAR, "A-001", LocalDate.of(2026, 1, 5));
        perpus.pinjam(ISBN_FILOSOFI, "A-001", LocalDate.of(2026, 1, 5));
        perpus.pinjam(ISBN_LAUT, "A-001", LocalDate.of(2026, 1, 5));
        periksa("buku yang sudah dipinjam ditolak", melempar(
                () -> perpus.pinjam(ISBN_LASKAR, "A-002", LocalDate.of(2026, 1, 5)),
                BukuDipinjamException.class));
        periksa("batas tiga buku per anggota", melempar(
                () -> perpus.pinjam(ISBN_BUMI, "A-001", LocalDate.of(2026, 1, 5)),
                BatasPeminjamanTercapaiException.class));

        perpus.kembalikan(ISBN_LASKAR, LocalDate.of(2026, 1, 10));
        Peminjaman pinjamSiti = perpus.pinjam(ISBN_LASKAR, "A-002", LocalDate.of(2026, 1, 11));
        periksa("buku yang dikembalikan dapat dipinjam lagi",
                pinjamSiti.anggota().id().equals("A-002"));

        perpus.kembalikan(ISBN_FILOSOFI, LocalDate.of(2026, 1, 10));
        periksa("pengembalian ganda ditolak", melempar(
                () -> perpus.kembalikan(ISBN_FILOSOFI, LocalDate.of(2026, 1, 12)),
                PerpustakaanException.class));
        periksa("buku yang tidak dipinjam tidak dapat dikembalikan", melempar(
                () -> perpus.kembalikan(ISBN_BUMI, LocalDate.of(2026, 1, 12)),
                PerpustakaanException.class));
    }

    static void ujiDenda() {
        System.out.println("== Denda ==");
        Perpustakaan perpus = perpusBaru();
        Peminjaman tepat = perpus.pinjam(ISBN_LASKAR, "A-001", LocalDate.of(2026, 1, 5));
        perpus.kembalikan(ISBN_LASKAR, LocalDate.of(2026, 1, 10));
        periksa("pengembalian tepat waktu berdenda nol",
                tepat.denda(LocalDate.of(2026, 1, 10), Perpustakaan.TARIF_DENDA_PER_HARI)
                        .compareTo(BigDecimal.ZERO) == 0);

        Peminjaman terlambat = perpus.pinjam(ISBN_FILOSOFI, "A-002", LocalDate.of(2026, 1, 5));
        perpus.kembalikan(ISBN_FILOSOFI, LocalDate.of(2026, 1, 25));
        periksa("enam hari terlambat berdenda 3000",
                terlambat.denda(LocalDate.of(2026, 1, 25), Perpustakaan.TARIF_DENDA_PER_HARI)
                        .compareTo(new BigDecimal("3000")) == 0);

        Peminjaman berjalan = perpus.pinjam(ISBN_LAUT, "A-001", LocalDate.of(2026, 1, 5));
        periksa("denda berjalan dihitung dari tanggal hitung",
                berjalan.denda(LocalDate.of(2026, 2, 1), Perpustakaan.TARIF_DENDA_PER_HARI)
                        .compareTo(new BigDecimal("6500")) == 0);
    }

    static void ujiPersistensi() throws PersistensiGagalException {
        System.out.println("== Persistensi ==");
        Perpustakaan perpus = perpusBaru();
        perpus.pinjam(ISBN_LASKAR, "A-001", LocalDate.of(2026, 1, 5));
        perpus.kembalikan(ISBN_LASKAR, LocalDate.of(2026, 1, 10));
        perpus.pinjam(ISBN_FILOSOFI, "A-002", LocalDate.of(2026, 1, 5));

        Path berkas = Path.of("uji-perpustakaan.txt");
        Path berkasRusak = Path.of("uji-rusak.txt");
        try {
            PenyimpananPerpustakaan.simpan(perpus, berkas);
            Perpustakaan salinan = PenyimpananPerpustakaan.muat(berkas);
            periksa("jumlah buku tetap setelah muat",
                    salinan.jumlahBuku() == perpus.jumlahBuku());
            periksa("jumlah anggota tetap setelah muat",
                    salinan.jumlahAnggota() == perpus.jumlahAnggota());
            periksa("riwayat tetap setelah muat",
                    salinan.riwayat().size() == perpus.riwayat().size());
            periksa("pinjaman aktif dipulihkan", salinan.jumlahPinjamanAktif() == 1);
            periksa("buku setara setelah perjalanan berkas",
                    salinan.cariBukuIsbn(ISBN_LASKAR).equals(perpus.cariBukuIsbn(ISBN_LASKAR)));

            // Baris tidak dikenal harus ditolak, bukan didiamkan.
            tulisBerkasRusak(berkasRusak);
            try {
                PenyimpananPerpustakaan.muat(berkasRusak);
                periksa("baris tidak dikenal ditolak", false);
            } catch (PersistensiGagalException e) {
                periksa("baris tidak dikenal ditolak",
                        e.getMessage().contains("baris tidak dikenal"));
            }
        } finally {
            hapusBilaAda(berkas);
            hapusBilaAda(berkasRusak);
        }
    }

    /** Cetak PASS/FAIL satu pernyataan dan akumulasikan jumlah kegagalan. */
    static void periksa(String nama, boolean kondisi) {
        if (kondisi) {
            System.out.println("PASS " + nama);
        } else {
            System.out.println("FAIL " + nama);
            gagal++;
        }
    }

    /** Benar bila aksi melempar exception jenis yang diharapkan. */
    static boolean melempar(Runnable aksi, Class<? extends Throwable> jenis) {
        try {
            aksi.run();
            return false; // tidak melempar apa pun: gagal
        } catch (Throwable t) {
            return jenis.isInstance(t);
        }
    }

    static void hapusBilaAda(Path berkas) {
        try {
            Files.deleteIfExists(berkas);
        } catch (IOException e) {
            // berkas uji tidak terhapus: tidak memengaruhi hasil pengujian
        }
    }

    /** Siapkan berkas berisi baris rusak; kegagalan menulis = galat persiapan uji. */
    static void tulisBerkasRusak(Path berkas) {
        try {
            Files.writeString(berkas, "SAMPAH|bukan|format\n", StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new RuntimeException("tidak dapat menulis berkas uji: " + berkas, e);
        }
    }

    static Perpustakaan perpusBaru() {
        Perpustakaan perpus = new Perpustakaan();
        perpus.tambahBuku(new Buku(ISBN_LASKAR, "Laskar Pelangi",
                "Andrea Hirata", 2005));
        perpus.tambahBuku(new Buku(ISBN_FILOSOFI, "Filosofi Teras",
                "Henry Manampiring", 2018));
        perpus.tambahBuku(new Buku(ISBN_LAUT, "Laut Bercerita",
                "Leila S. Chudori", 2017));
        perpus.tambahBuku(new Buku(ISBN_BUMI, "Bumi Manusia",
                "Pramoedya Ananta Toer", 1980));
        perpus.daftarkan(new Anggota("A-001", "Budi Santoso",
                "budi@example.com", LocalDate.of(2026, 1, 2)));
        perpus.daftarkan(new Anggota("A-002", "Siti Rahayu",
                "siti@example.com", LocalDate.of(2026, 1, 3)));
        return perpus;
    }
}
