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.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

// Penyimpanan berkas: data bertahan melewati akhir program. Satu berkas
// memuat dua "tabel": baris BARANG;... dan baris MUTASI;...
public final class GudangRepositoryBerkas implements GudangRepository {

    public static final String HEADER = "GUDANG;1";

    private final Path berkas;
    private final Map<String, Barang> tabelBarang = new LinkedHashMap<>();
    private final List<MutasiStok> tabelMutasi = new ArrayList<>();
    private int idBerikutnya = 1;

    /** Membuka penyimpanan berkas; isi lama dimuat bila berkasnya ada. */
    public GudangRepositoryBerkas(Path berkas) {
        this.berkas = berkas;
        if (Files.exists(berkas)) {
            muat();
        }
    }

    @Override
    public void simpan(Barang barang) {
        tolakPemisah(barang.nama(), "nama barang");
        tolakPemisah(barang.satuan(), "satuan");
        Barang sebelumnya = tabelBarang.putIfAbsent(barang.kode(), barang);
        if (sebelumnya != null) {
            throw new GudangException("kode barang sudah terdaftar: " + barang.kode());
        }
        tulis();
    }

    @Override
    public Barang cari(String kode) {
        return tabelBarang.get(kode);
    }

    @Override
    public List<Barang> semuaBarang() {
        return tabelBarang.values().stream()
                .sorted(Comparator.comparing(Barang::kode))
                .toList();
    }

    @Override
    public void perbaruiStok(String kode, int stokBaru) {
        Barang barang = cariWajib(kode);
        tabelBarang.put(kode, barang.denganStok(stokBaru));
        tulis();
    }

    @Override
    public MutasiStok catat(MutasiStok mutasi) {
        tolakPemisah(mutasi.petugas(), "petugas");
        tolakPemisah(mutasi.catatan(), "catatan");
        MutasiStok tersimpan = new MutasiStok(idBerikutnya, mutasi.kodeBarang(),
                mutasi.jenis(), mutasi.jumlah(), mutasi.tanggal(),
                mutasi.petugas(), mutasi.catatan());
        idBerikutnya++;
        tabelMutasi.add(tersimpan);
        tulis();
        return tersimpan;
    }

    @Override
    public List<MutasiStok> riwayat() {
        return List.copyOf(tabelMutasi);
    }

    /** Menulis ulang seluruh isi ke berkas dengan charset UTF-8. */
    private void tulis() {
        List<String> baris = new ArrayList<>();
        baris.add(HEADER);
        for (Barang b : semuaBarang()) {
            baris.add("BARANG;" + b.kode() + ";" + b.nama() + ";" + b.satuan() + ";"
                    + b.harga().toPlainString() + ";" + b.stok() + ";" + b.stokMinimum());
        }
        for (MutasiStok m : tabelMutasi) {
            baris.add("MUTASI;" + m.id() + ";" + m.kodeBarang() + ";" + m.jenis() + ";"
                    + m.jumlah() + ";" + m.tanggal() + ";" + m.petugas() + ";" + m.catatan());
        }
        try {
            Files.write(berkas, baris, StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new GudangException("gagal menulis berkas gudang: " + berkas, e);
        }
    }

    /** Memuat isi berkas; baris cacat menghentikan pemuatan dengan nomor barisnya. */
    private void muat() {
        List<String> isi;
        try {
            isi = Files.readAllLines(berkas, StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new GudangException("gagal membaca berkas gudang: " + berkas, e);
        }
        if (isi.isEmpty() || !HEADER.equals(isi.get(0))) {
            throw new GudangException("header tidak dikenali pada berkas: " + berkas);
        }
        for (int i = 1; i < isi.size(); i++) {
            String baris = isi.get(i);
            try {
                if (baris.startsWith("BARANG;")) {
                    Barang b = parseBarang(baris);
                    tabelBarang.put(b.kode(), b);
                } else if (baris.startsWith("MUTASI;")) {
                    MutasiStok m = parseMutasi(baris);
                    tabelMutasi.add(m);
                    idBerikutnya = Math.max(idBerikutnya, m.id() + 1);
                } else {
                    throw new IllegalArgumentException("awalan baris tidak dikenali");
                }
            } catch (RuntimeException e) {
                throw new GudangException(
                        "baris " + (i + 1) + " tidak terbaca: " + e.getMessage(), e);
            }
        }
    }

    private static Barang parseBarang(String baris) {
        String[] kolom = baris.split(";", -1);
        if (kolom.length != 7) {
            throw new IllegalArgumentException("kolom barang " + kolom.length + ", harap 7");
        }
        BigDecimal harga;
        try {
            harga = new BigDecimal(kolom[4]);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("harga '" + kolom[4] + "' bukan angka");
        }
        return new Barang(kolom[1], kolom[2], kolom[3], harga,
                Integer.parseInt(kolom[5]), Integer.parseInt(kolom[6]));
    }

    private static MutasiStok parseMutasi(String baris) {
        String[] kolom = baris.split(";", -1);
        if (kolom.length != 8) {
            throw new IllegalArgumentException("kolom mutasi " + kolom.length + ", harap 8");
        }
        LocalDate tanggal;
        try {
            tanggal = LocalDate.parse(kolom[5]);
        } catch (DateTimeParseException e) {
            throw new IllegalArgumentException("tanggal '" + kolom[5] + "' tidak valid");
        }
        return new MutasiStok(Integer.parseInt(kolom[1]), kolom[2],
                JenisMutasi.valueOf(kolom[3]), Integer.parseInt(kolom[4]),
                tanggal, kolom[6], kolom[7]);
    }

    private Barang cariWajib(String kode) {
        Barang barang = tabelBarang.get(kode);
        if (barang == null) {
            throw new GudangException("kode barang tidak terdaftar: " + kode);
        }
        return barang;
    }

    /** Kolom teks tidak boleh mengandung ';' karena bertabrakan dengan pemisah. */
    private static void tolakPemisah(String nilai, String kolom) {
        if (nilai.indexOf(';') >= 0) {
            throw new GudangException(kolom + " tidak boleh mengandung ';': " + nilai);
        }
    }
}
