import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

/**
 * Pengujian perilaku inti aplikasi tanpa pustaka eksternal.
 * Setiap pemeriksaan mencetak PASS atau FAIL; exit code 0 berarti semua lulus.
 */
public class UjiDaftarTugas {

    private static int gagal = 0;

    public static void main(String[] args) throws Exception {
        ujiTambah();
        ujiValidasiJudul();
        ujiTandaiSelesai();
        ujiSimpanMuat();
        ujiBerkasRusak();
        System.out.println();
        System.out.println("Pengujian selesai; jumlah gagal: " + gagal + ".");
        if (gagal > 0) {
            System.exit(1);
        }
    }

    private static void ujiTambah() throws MasukanInvalidException {
        DaftarTugas daftar = new DaftarTugas();
        Tugas tugas = daftar.tambah("  Belajar Java dasar  ");
        periksa("tambah mengembalikan id pertama 1", tugas.id() == 1);
        periksa("tambah memangkas spasi judul",
                tugas.judul().equals("Belajar Java dasar"));
        periksa("tugas baru belum selesai", !tugas.selesai());
        Tugas kedua = daftar.tambah("Baca dokumentasi Files");
        periksa("tugas kedua mendapat id 2", kedua.id() == 2);
    }

    private static void ujiValidasiJudul() {
        periksa("judul kosong ditolak", ditolak("   "));
        periksa("judul 101 karakter ditolak", ditolak("x".repeat(101)));
        periksa("judul memuat karakter | ditolak", ditolak("Beli|Pulpen"));
    }

    private static void ujiTandaiSelesai()
            throws MasukanInvalidException, TugasTidakDitemukanException {
        DaftarTugas daftar = new DaftarTugas();
        daftar.tambah("Belajar Java dasar");
        periksa("id bukan angka ditolak",
                lempar(() -> daftar.tandaiSelesai("abc"),
                        MasukanInvalidException.class));
        periksa("id tidak ada ditolak",
                lempar(() -> daftar.tandaiSelesai("9"),
                        TugasTidakDitemukanException.class));
        Tugas tugas = daftar.tandaiSelesai("1");
        periksa("tugas bisa ditandai selesai", tugas.selesai());
        periksa("format baris selesai benar",
                tugas.formatBaris().equals("[x] 1. Belajar Java dasar"));
    }

    private static void ujiSimpanMuat() throws Exception {
        Path berkas = Path.of("uji-tugas.txt");
        DaftarTugas daftar = new DaftarTugas();
        daftar.tambah("Belajar Java dasar");
        daftar.tambah("Baca dokumentasi Files");
        daftar.tandaiSelesai("2");

        PenyimpananTugas penyimpanan = new PenyimpananTugas(berkas);
        penyimpanan.simpan(daftar);

        periksa("berkas tersimpan", Files.exists(berkas));
        periksa("baris pertama berkas benar",
                Files.readAllLines(berkas, StandardCharsets.UTF_8).get(0)
                        .equals("1|Belajar Java dasar|aktif"));

        DaftarTugas hasil = penyimpanan.muat();
        periksa("jumlah tugas termuat kembali",
                hasil.semua().size() == 2);
        periksa("judul termuat kembali",
                hasil.semua().get(0).judul().equals("Belajar Java dasar"));
        periksa("status selesai termuat kembali",
                hasil.semua().get(1).selesai());
    }

    private static void ujiBerkasRusak() throws IOException {
        Path berkas = Path.of("uji-rusak.txt");
        Files.writeString(berkas, "baris tanpa pemisah\n", StandardCharsets.UTF_8);
        PenyimpananTugas penyimpanan = new PenyimpananTugas(berkas);
        periksa("baris rusak memicu IOException",
                lempar(penyimpanan::muat, IOException.class));
    }

    /** Mencetak hasil satu pemeriksaan dan menghitung kegagalan. */
    private static void periksa(String nama, boolean kondisi) {
        if (kondisi) {
            System.out.println("PASS " + nama);
        } else {
            System.out.println("FAIL " + nama);
            gagal++;
        }
    }

    /** Menjalankan tambah; true bila judul ditolak oleh validasi. */
    private static boolean ditolak(String judul) {
        try {
            new DaftarTugas().tambah(judul);
            return false;
        } catch (MasukanInvalidException e) {
            return true;
        }
    }

    /** Menjalankan perintah; true bila melempar exception bertipe persis. */
    private static boolean lempar(Perintah perintah,
            Class<? extends Exception> jenis) {
        try {
            perintah.jalankan();
            return false;
        } catch (Exception e) {
            return e.getClass() == jenis;
        }
    }

    /** Antarmuka satu method agar blok uji bisa melempar exception terperiksa. */
    interface Perintah {
        void jalankan() throws Exception;
    }
}
