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

/** Kumpulan transaksi satu pemilik, kini dengan kategori terdaftar dan anggaran bulanan. */
public class BukuKas {

    private final Map<String, Kategori> kategori = new LinkedHashMap<>();
    private final List<Transaksi> daftar = new ArrayList<>();

    /** Mendaftarkan kategori baru; nama yang sama tidak boleh didaftarkan dua kali. */
    public void daftarkanKategori(Kategori k) {
        if (k == null) {
            throw new IllegalArgumentException("Kategori tidak boleh null");
        }
        if (kategori.containsKey(k.nama())) {
            throw new IllegalStateException("Kategori '" + k.nama() + "' sudah terdaftar");
        }
        kategori.put(k.nama(), k);
    }

    public Kategori cariKategori(String nama) {
        return kategori.get(nama);
    }

    /** Menambah transaksi setelah kategorinya terbukti terdaftar dan cocok jenisnya. */
    public void tambah(Transaksi t) throws KategoriTidakTerdahtarException {
        Kategori k = kategori.get(t.kategori());
        if (k == null) {
            throw new KategoriTidakTerdahtarException(
                    "Kategori '" + t.kategori() + "' belum terdaftar di BukuKas");
        }
        if (k.jenis() != t.jenis()) {
            throw new IllegalArgumentException("Kategori '" + k.nama() + "' dikhususkan untuk "
                    + k.jenis() + ", transaksi ini berjenis " + t.jenis());
        }
        daftar.add(t);
    }

    /** Daftar transaksi baca-saja agar pemanggil tidak dapat mengubah catatan dari luar. */
    public List<Transaksi> semua() {
        return Collections.unmodifiableList(daftar);
    }

    public int jumlahTransaksi() {
        return daftar.size();
    }

    /** Menjumlahkan nominal seluruh transaksi satu jenis. */
    public BigDecimal total(JenisTransaksi jenis) {
        BigDecimal total = BigDecimal.ZERO;
        for (Transaksi t : daftar) {
            if (t.jenis() == jenis) {
                total = total.add(t.jumlah());
            }
        }
        return total;
    }

    /** Saldo = total pemasukan dikurangi total pengeluaran. */
    public BigDecimal saldo() {
        return total(JenisTransaksi.PEMASUKAN)
                .subtract(total(JenisTransaksi.PENGELUARAN));
    }

    /**
     * Realisasi pengeluaran per kategori pada satu bulan.
     * Seluruh kategori pengeluaran ikut hadir; kategori tanpa transaksi bernilai nol.
     */
    public Map<String, BigDecimal> realisasiPengeluaran(YearMonth bulan) {
        Map<String, BigDecimal> hasil = new LinkedHashMap<>();
        for (Kategori k : kategori.values()) {
            if (k.jenis() == JenisTransaksi.PENGELUARAN) {
                hasil.put(k.nama(), BigDecimal.ZERO);
            }
        }
        for (Transaksi t : daftar) {
            if (t.jenis() == JenisTransaksi.PENGELUARAN && t.bulan().equals(bulan)) {
                hasil.merge(t.kategori(), t.jumlah(), BigDecimal::add);
            }
        }
        return hasil;
    }

    /** Kategori pengeluaran beranggaran dalam urutan pendaftaran, untuk laporan anggaran. */
    public List<Kategori> kategoriBeranggaran() {
        List<Kategori> hasil = new ArrayList<>();
        for (Kategori k : kategori.values()) {
            if (k.beranggaran()) {
                hasil.add(k);
            }
        }
        return hasil;
    }
}
