import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

// Layanan perpustakaan: katalog, keanggotaan, dan sirkulasi peminjaman.
public class Perpustakaan {

    /** Jumlah buku maksimum yang boleh dipinjam satu anggota. */
    public static final int BATAS_PINJAMAN_PER_ANGGOTA = 3;

    /** Denda keterlambatan per hari. */
    public static final BigDecimal TARIF_DENDA_PER_HARI = new BigDecimal("500");

    private final Map<String, Buku> katalog = new LinkedHashMap<>();
    private final Map<String, Anggota> anggota = new LinkedHashMap<>();

    // Invarian: satu ISBN hanya boleh punya satu pinjaman aktif.
    private final Map<String, Peminjaman> pinjamanAktif = new LinkedHashMap<>();
    private final List<Peminjaman> riwayat = new ArrayList<>();

    public void tambahBuku(Buku buku) {
        Buku sebelumnya = katalog.putIfAbsent(buku.isbn(), buku);
        if (sebelumnya != null) {
            throw new PerpustakaanException(
                    "ISBN sudah terdaftar: " + buku.isbn());
        }
    }

    public void daftarkan(Anggota baru) {
        Anggota sebelumnya = anggota.putIfAbsent(baru.id(), baru);
        if (sebelumnya != null) {
            throw new PerpustakaanException(
                    "ID anggota sudah terdaftar: " + baru.id());
        }
    }

    public Buku cariBukuIsbn(String isbn) {
        Buku buku = katalog.get(isbn);
        if (buku == null) {
            throw new BukuTidakDitemukanException(
                    "ISBN tidak ada di katalog: " + isbn);
        }
        return buku;
    }

    public Anggota cariAnggota(String id) {
        Anggota dicari = anggota.get(id);
        if (dicari == null) {
            throw new AnggotaTidakDitemukanException(
                    "ID anggota tidak terdaftar: " + id);
        }
        return dicari;
    }

    public List<Buku> cariBuku(String kataKunci) {
        List<Buku> hasil = new ArrayList<>();
        for (Buku buku : katalog.values()) {
            if (buku.cocokKataKunci(kataKunci)) {
                hasil.add(buku);
            }
        }
        return hasil;
    }

    /**
     * Pinjam sebuah buku. Melempar BukuTidakDitemukanException,
     * AnggotaTidakDitemukanException, BukuDipinjamException, atau
     * BatasPeminjamanTercapaiException bila aturan dilanggar.
     */
    public Peminjaman pinjam(String isbn, String idAnggota, LocalDate tanggal) {
        Buku buku = cariBukuIsbn(isbn);
        Anggota peminjam = cariAnggota(idAnggota);
        if (pinjamanAktif.containsKey(isbn)) {
            throw new BukuDipinjamException(
                    "buku sudah dipinjam oleh " + pinjamanAktif.get(isbn).anggota().nama()
                            + ": " + buku.judul());
        }
        if (hitungPinjamanAktif(idAnggota) >= BATAS_PINJAMAN_PER_ANGGOTA) {
            throw new BatasPeminjamanTercapaiException(
                    "anggota " + peminjam.nama() + " sudah memegang "
                            + BATAS_PINJAMAN_PER_ANGGOTA + " buku");
        }
        Peminjaman baru = new Peminjaman(buku, peminjam, tanggal);
        pinjamanAktif.put(isbn, baru);
        riwayat.add(baru);
        return baru;
    }

    /**
     * Kembalikan buku yang sedang dipinjam. Transaksi hanya dilepas dari
     * sirkulasi setelah pengembalian dinyatakan sah oleh Peminjaman.
     */
    public Peminjaman kembalikan(String isbn, LocalDate tanggal) {
        Peminjaman pinjaman = pinjamanAktif.get(isbn);
        if (pinjaman == null) {
            throw new PerpustakaanException(
                    "buku tidak sedang dipinjam: " + isbn);
        }
        pinjaman.kembalikan(tanggal); // bisa melempar bila tanggal tidak sah
        pinjamanAktif.remove(isbn);   // dijalankan hanya bila pengembalian sah
        return pinjaman;
    }

    /** Pinjaman aktif milik satu anggota, dalam urutan peminjaman. */
    public List<Peminjaman> pinjamanAktifAnggota(String idAnggota) {
        List<Peminjaman> hasil = new ArrayList<>();
        for (Peminjaman p : pinjamanAktif.values()) {
            if (p.anggota().id().equals(idAnggota)) {
                hasil.add(p);
            }
        }
        return hasil;
    }

    /** Seluruh transaksi (aktif dan selesai) dalam urutan kejadian. */
    public List<Peminjaman> riwayat() {
        return List.copyOf(riwayat);
    }

    public int jumlahPinjamanAktif() {
        return pinjamanAktif.size();
    }

    public List<Buku> semuaBuku() {
        return List.copyOf(katalog.values());
    }

    public List<Anggota> semuaAnggota() {
        return List.copyOf(anggota.values());
    }

    public int jumlahBuku() {
        return katalog.size();
    }

    public int jumlahAnggota() {
        return anggota.size();
    }

    private int hitungPinjamanAktif(String idAnggota) {
        int jumlah = 0;
        for (Peminjaman p : pinjamanAktif.values()) {
            if (p.anggota().id().equals(idAnggota)) {
                jumlah++;
            }
        }
        return jumlah;
    }
}
