import java.io.IOException;
import java.io.UncheckedIOException;
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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * Penyimpanan berkas berbasis baris: satu tugas satu baris dengan format
 * T;id;judul;pemilik;status;tenggat. Seluruh berkas dimuat saat konstruksi
 * dan ditulis ulang pada setiap perubahan — sederhana dan tepat untuk data
 * seukuran proyek ini; database mengambil alih saat data bertambah besar.
 */
public class RepositoryBerkas implements TugasRepository {

    private final Path berkas;
    private final Map<Integer, Tugas> data = new LinkedHashMap<>();
    private int urutan = 0;

    public RepositoryBerkas(Path berkas) {
        this.berkas = berkas;
        muat();
    }

    @Override
    public Tugas tambah(Tugas tugas) {
        tolakPemisah(tugas);
        urutan++;
        Tugas tersimpan = tugas.denganId(urutan);
        data.put(urutan, tersimpan);
        tulisUlang();
        return tersimpan;
    }

    @Override
    public Optional<Tugas> cari(int id) {
        return Optional.ofNullable(data.get(id));
    }

    @Override
    public List<Tugas> semua() {
        return new ArrayList<>(data.values());
    }

    @Override
    public Optional<Tugas> simpan(Tugas tugas) {
        if (!data.containsKey(tugas.id())) {
            return Optional.empty();
        }
        tolakPemisah(tugas);
        data.put(tugas.id(), tugas);
        tulisUlang();
        return Optional.of(tugas);
    }

    @Override
    public boolean hapus(int id) {
        boolean ada = data.remove(id) != null;
        if (ada) {
            tulisUlang();
        }
        return ada;
    }

    /** Memuat seluruh baris; baris cacat menghentikan proses karena integritas diragukan. */
    private void muat() {
        if (!Files.exists(berkas)) {
            return;   // berkas belum pernah dibuat: mulai dari kondisi kosong
        }
        List<String> baris;
        try {
            baris = Files.readAllLines(berkas, StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new UncheckedIOException("gagal membaca " + berkas, e);
        }
        for (int i = 0; i < baris.size(); i++) {
            String satu = baris.get(i);
            if (satu.isBlank()) {
                continue;
            }
            Tugas tugas = parseBaris(satu, i + 1);
            urutan = Math.max(urutan, tugas.id());
            data.put(tugas.id(), tugas);
        }
    }

    /** Menulis ulang seluruh berkas dalam charset UTF-8. */
    private void tulisUlang() {
        List<String> baris = new ArrayList<>();
        for (Tugas t : data.values()) {
            baris.add("T;" + t.id() + ";" + t.judul() + ";" + t.pemilik()
                    + ";" + t.status() + ";" + t.tenggat());
        }
        try {
            Files.write(berkas, baris, StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new UncheckedIOException("gagal menulis " + berkas, e);
        }
    }

    /**
     * Karakter ';' dipakai sebagai pemisah kolom, sehingga judul dan pemilik
     * yang memuatnya ditolak lebih awal agar berkas tetap bisa dibaca ulang.
     */
    private static void tolakPemisah(Tugas tugas) {
        if (tugas.judul().indexOf(';') >= 0 || tugas.pemilik().indexOf(';') >= 0) {
            throw new IllegalArgumentException("judul dan pemilik tidak boleh mengandung "
                    + "';' karena karakter itu dipakai sebagai pemisah kolom berkas");
        }
    }

    /** Mengubah satu baris berkas menjadi Tugas dengan laporan nomor baris. */
    private static Tugas parseBaris(String baris, int nomor) {
        if (!baris.startsWith("T;")) {
            throw new IllegalStateException("baris " + nomor + ": awalan tidak dikenal");
        }
        String[] kolom = baris.split(";", -1);
        if (kolom.length != 6) {
            throw new IllegalStateException("baris " + nomor + ": jumlah kolom "
                    + kolom.length + ", harap 6");
        }
        int id;
        try {
            id = Integer.parseInt(kolom[1]);
        } catch (NumberFormatException e) {
            throw new IllegalStateException("baris " + nomor + ": id '"
                    + kolom[1] + "' bukan angka");
        }
        LocalDate tenggat;
        try {
            tenggat = LocalDate.parse(kolom[5]);
        } catch (DateTimeParseException e) {
            throw new IllegalStateException("baris " + nomor + ": tenggat '"
                    + kolom[5] + "' tidak valid");
        }
        StatusTugas status;
        try {
            status = StatusTugas.dariTeks(kolom[4]);
        } catch (IllegalArgumentException e) {
            throw new IllegalStateException("baris " + nomor + ": " + e.getMessage());
        }
        return new Tugas(id, kolom[2], kolom[3], status, tenggat);
    }
}
