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;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;

/**
 * Menyimpan dan memuat daftar transaksi lewat berkas teks berformat baris.
 * Tiap baris data berbentuk T;tanggal;keterangan;jenis;kategori;nominal,
 * seluruh berkas ditutup satu baris CHECKSUM.
 */
public final class PenyimpananKas {

    public static final String HEADER = "BUKUKAS;1";
    public static final String PREFIX_DATA = "T;";
    public static final String PREFIX_CHECKSUM = "CHECKSUM;";

    /** Hasil memuat: transaksi yang lolos validasi plus daftar galat per baris. */
    public record HasilMuat(List<Transaksi> transaksi, List<String> galat) {

        /** Benar-benar bersih bila tidak ada satu pun galat. */
        public boolean bersih() {
            return galat.isEmpty();
        }
    }

    private PenyimpananKas() {
        // Kelas utilitas: seluruh anggota static, tidak perlu instance.
    }

    /** Menulis seluruh transaksi ke berkas UTF-8, ditutup baris checksum. */
    public static void simpan(List<Transaksi> transaksi, Path berkas) throws IOException {
        List<String> baris = new ArrayList<>();
        baris.add(HEADER);
        for (Transaksi t : transaksi) {
            if (t.keterangan().indexOf(';') >= 0 || t.kategori().indexOf(';') >= 0) {
                throw new IllegalArgumentException("Keterangan dan kategori tidak boleh "
                        + "mengandung ';' karena bertabrakan dengan pemisah kolom");
            }
            baris.add(PREFIX_DATA + t.tanggal() + ";" + t.keterangan() + ";" + t.jenis()
                    + ";" + t.kategori() + ";" + t.jumlah().toPlainString());
        }
        baris.add(PREFIX_CHECKSUM + checksum(baris));
        Files.write(berkas, baris, StandardCharsets.UTF_8);
    }

    /**
     * Checksum sederhana: penjumlahan nilai karakter Unicode seluruh baris yang diberikan.
     * Angka ini bukan pengaman kriptografi, hanya pendeteksi perubahan tak disengaja.
     */
    public static long checksum(List<String> baris) {
        long total = 0;
        for (String b : baris) {
            for (int i = 0; i < b.length(); i++) {
                total += b.charAt(i);
            }
        }
        return total;
    }

    /**
     * Memuat berkas. Baris cacat ditolak satu per satu dengan laporan;
     * checksum yang tidak selaras menolak seluruh isi karena integritas data diragukan.
     */
    public static HasilMuat muat(Path berkas) throws IOException {
        List<String> isi = Files.readAllLines(berkas, StandardCharsets.UTF_8);
        List<Transaksi> valid = new ArrayList<>();
        List<String> galat = new ArrayList<>();

        if (isi.isEmpty() || !HEADER.equals(isi.get(0))) {
            galat.add("Baris 1: header '"
                    + (isi.isEmpty() ? "(berkas kosong)" : isi.get(0))
                    + "' tidak dikenali, harap '" + HEADER + "'");
            return new HasilMuat(List.of(), galat);
        }
        if (!cekChecksum(isi, galat)) {
            return new HasilMuat(List.of(), galat);
        }

        for (int i = 1; i < isi.size() - 1; i++) {
            String baris = isi.get(i);
            if (baris.isBlank()) {
                galat.add("Baris " + (i + 1) + ": baris kosong");
            } else if (!baris.startsWith(PREFIX_DATA)) {
                galat.add("Baris " + (i + 1) + ": awalan baris tidak dikenali");
            } else {
                try {
                    valid.add(parse(baris));
                } catch (IllegalArgumentException e) {
                    galat.add("Baris " + (i + 1) + ": " + e.getMessage());
                }
            }
        }
        return new HasilMuat(valid, galat);
    }

    /** Membandingkan checksum tercatat pada footer dengan hasil hitung ulang. */
    private static boolean cekChecksum(List<String> isi, List<String> galat) {
        String footer = isi.get(isi.size() - 1);
        if (!footer.startsWith(PREFIX_CHECKSUM)) {
            galat.add("Baris " + isi.size() + ": footer CHECKSUM tidak ditemukan");
            return false;
        }
        long tercatat;
        try {
            tercatat = Long.parseLong(footer.substring(PREFIX_CHECKSUM.length()));
        } catch (NumberFormatException e) {
            galat.add("Baris " + isi.size() + ": angka checksum tidak terbaca");
            return false;
        }
        long dihitung = checksum(isi.subList(0, isi.size() - 1));
        if (tercatat != dihitung) {
            galat.add("Checksum tidak selaras: tercatat " + tercatat
                    + ", dihitung " + dihitung);
            return false;
        }
        return true;
    }

    /** Memecah satu baris data menjadi Transaksi dengan pesan galat berbahasa Indonesia. */
    private static Transaksi parse(String baris) {
        String[] kolom = baris.split(";", -1);
        if (kolom.length != 6) {
            throw new IllegalArgumentException("jumlah kolom " + kolom.length + ", harap 6");
        }
        LocalDate tanggal;
        try {
            tanggal = LocalDate.parse(kolom[1]);
        } catch (DateTimeParseException e) {
            throw new IllegalArgumentException("tanggal '" + kolom[1] + "' tidak valid");
        }
        JenisTransaksi jenis;
        try {
            jenis = JenisTransaksi.valueOf(kolom[3]);
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("jenis '" + kolom[3] + "' tidak dikenal");
        }
        BigDecimal nominal;
        try {
            nominal = new BigDecimal(kolom[5]);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("nominal '" + kolom[5] + "' bukan angka");
        }
        return new Transaksi(tanggal, kolom[2], jenis, kolom[4], nominal);
    }
}
