import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

/**
 * Logika inti aplikasi Buku Kontak: daftar kontak dengan validasi,
 * operasi CRUD, dan pencarian. Kelas ini tidak mengenal satu pun
 * kelas Swing sehingga bisa diuji tanpa layar.
 */
public class DaftarKontak {

    private final KontakDao dao;
    private final List<Kontak> kontak = new ArrayList<>();
    private int idBerikutnya = 1;

    /** Daftar kontak tanpa penyimpanan permanen (penyimpanan memori). */
    public DaftarKontak() {
        this.dao = new KontakDaoMemori();
    }

    /**
     * Daftar kontak yang dimuat dari penyimpanan dan menulis
     * kembali ke penyimpanan yang sama pada setiap perubahan.
     */
    public DaftarKontak(KontakDao dao) throws PersistensiGagalException {
        this.dao = dao;
        kontak.addAll(dao.muatSemua());
        for (Kontak k : kontak) {
            idBerikutnya = Math.max(idBerikutnya, k.id() + 1);
        }
    }

    /** Menambah kontak baru setelah seluruh field tervalidasi. */
    public Kontak tambah(String nama, String email, String telepon)
            throws KontakInvalidException, PersistensiGagalException {
        periksa(nama, email, telepon);
        periksaEmailBelumTerpakai(email, 0);

        Kontak baru = new Kontak(idBerikutnya,
                nama.strip(), email.strip(), telepon.strip());
        dao.tambah(baru);
        kontak.add(baru);
        idBerikutnya++;
        return baru;
    }

    /** Menimpa kontak berdasarkan id dengan nilai field yang baru. */
    public Kontak ubah(int id, String nama, String email, String telepon)
            throws KontakInvalidException, KontakTidakDitemukanException,
            PersistensiGagalException {
        periksa(nama, email, telepon);
        periksaEmailBelumTerpakai(email, id);

        int posisi = indeksDariId(id);
        if (posisi < 0) {
            throw new KontakTidakDitemukanException(id);
        }
        Kontak baru = new Kontak(id,
                nama.strip(), email.strip(), telepon.strip());
        dao.perbarui(baru);
        kontak.set(posisi, baru);
        return baru;
    }

    /** Menghapus kontak berdasarkan id dan mengembalikan kontaknya. */
    public Kontak hapus(int id)
            throws KontakTidakDitemukanException, PersistensiGagalException {
        int posisi = indeksDariId(id);
        if (posisi < 0) {
            throw new KontakTidakDitemukanException(id);
        }
        Kontak terhapus = kontak.remove(posisi);
        dao.hapus(id);
        return terhapus;
    }

    /** Salinan seluruh kontak, terurut sesuai urutan penambahan. */
    public List<Kontak> semua() {
        return List.copyOf(kontak);
    }

    /** Kontak berdasarkan id, atau kosong bila tidak ada. */
    public Optional<Kontak> cari(int id) {
        for (Kontak k : kontak) {
            if (k.id() == id) {
                return Optional.of(k);
            }
        }
        return Optional.empty();
    }

    /** Kontak yang nama, email, atau teleponnya memuat kata kunci. */
    public List<Kontak> cari(String kataKunci) {
        String kunci = kataKunci.strip().toLowerCase(Locale.ROOT);
        List<Kontak> hasil = new ArrayList<>();
        for (Kontak k : kontak) {
            if (k.nama().toLowerCase(Locale.ROOT).contains(kunci)
                    || k.email().toLowerCase(Locale.ROOT).contains(kunci)
                    || k.telepon().toLowerCase(Locale.ROOT).contains(kunci)) {
                hasil.add(k);
            }
        }
        return hasil;
    }

    public int jumlah() {
        return kontak.size();
    }

    private int indeksDariId(int id) {
        for (int i = 0; i < kontak.size(); i++) {
            if (kontak.get(i).id() == id) {
                return i;
            }
        }
        return -1;
    }

    /** Validasi seluruh field sebelum kontak disimpan. */
    private static void periksa(String nama, String email, String telepon)
            throws KontakInvalidException {
        if (nama == null || nama.isBlank()) {
            throw new KontakInvalidException("nama tidak boleh kosong");
        }
        if (email == null || email.isBlank()) {
            throw new KontakInvalidException("email tidak boleh kosong");
        }
        if (telepon == null || telepon.isBlank()) {
            throw new KontakInvalidException("telepon tidak boleh kosong");
        }
        periksaFormatEmail(email.strip());
        periksaTanpaPemisah(nama.strip(), email.strip(), telepon.strip());
    }

    /** Email harus berformat pengguna@domain.ekstensi. */
    private static void periksaFormatEmail(String email)
            throws KontakInvalidException {
        int posisiAt = email.indexOf('@');
        if (posisiAt <= 0 || posisiAt != email.lastIndexOf('@')
                || email.indexOf('.', posisiAt) < 0) {
            throw new KontakInvalidException(
                    "email harus berformat pengguna@domain.ekstensi: " + email);
        }
    }

    /** Karakter | dipakai sebagai pemisah kolom pada berkas penyimpanan. */
    private static void periksaTanpaPemisah(String nama, String email, String telepon)
            throws KontakInvalidException {
        if (nama.indexOf('|') >= 0 || email.indexOf('|') >= 0
                || telepon.indexOf('|') >= 0) {
            throw new KontakInvalidException(
                    "field tidak boleh memuat karakter |");
        }
    }

    /** Email hanya boleh dipakai satu kontak. */
    private void periksaEmailBelumTerpakai(String email, int idKecuali)
            throws KontakInvalidException {
        for (Kontak k : kontak) {
            if (k.id() != idKecuali
                    && k.email().equalsIgnoreCase(email.strip())) {
                throw new KontakInvalidException(
                        "email sudah dipakai kontak lain: " + email.strip());
            }
        }
    }
}
