package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"slices"
	"sync"
)

// ErrNotFound adalah error domain untuk data yang tidak ditemukan.
var ErrNotFound = errors.New("pelanggan tidak ditemukan")

// Pelanggan adalah struct domain murni tanpa dependensi database.
type Pelanggan struct {
	ID    int    `json:"id"`
	Nama  string `json:"nama"`
	Email string `json:"email"`
}

// FileStore menyimpan pelanggan pada berkas JSON.
// Tipe ini menggantikan database pada unit test: test membuat berkas
// di t.TempDir() sehingga setiap test mendapat folder bersih sendiri.
type FileStore struct {
	mu      sync.Mutex
	berkas  string
	berikut int
}

func NewFileStore(berkas string) *FileStore {
	return &FileStore{berkas: berkas, berikut: 1}
}

// muat membaca seluruh pelanggan dari berkas; berkas yang belum ada
// berarti penyimpanan masih kosong, bukan error.
func (s *FileStore) muat() ([]Pelanggan, error) {
	data, err := os.ReadFile(s.berkas)
	if errors.Is(err, os.ErrNotExist) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	var daftar []Pelanggan
	if err := json.Unmarshal(data, &daftar); err != nil {
		return nil, err
	}
	return daftar, nil
}

func (s *FileStore) simpan(daftar []Pelanggan) error {
	data, err := json.MarshalIndent(daftar, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(s.berkas, data, 0o600)
}

func (s *FileStore) Create(nama, email string) (Pelanggan, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	daftar, err := s.muat()
	if err != nil {
		return Pelanggan{}, err
	}
	p := Pelanggan{ID: s.berikut, Nama: nama, Email: email}
	daftar = append(daftar, p)
	if err := s.simpan(daftar); err != nil {
		return Pelanggan{}, err
	}
	s.berikut++
	return p, nil
}

func (s *FileStore) Get(id int) (Pelanggan, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	daftar, err := s.muat()
	if err != nil {
		return Pelanggan{}, err
	}
	for _, p := range daftar {
		if p.ID == id {
			return p, nil
		}
	}
	return Pelanggan{}, ErrNotFound
}

func (s *FileStore) List() ([]Pelanggan, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	daftar, err := s.muat()
	if err != nil {
		return nil, err
	}
	slices.SortFunc(daftar, func(a, b Pelanggan) int {
		return a.ID - b.ID
	})
	return daftar, nil
}

func (s *FileStore) Delete(id int) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	daftar, err := s.muat()
	if err != nil {
		return err
	}
	for i, p := range daftar {
		if p.ID == id {
			daftar = slices.Delete(daftar, i, i+1)
			return s.simpan(daftar)
		}
	}
	return ErrNotFound
}

func main() {
	// Demo memakai folder sementara supaya tidak meninggalkan jejak.
	// Pada test, folder ini digantikan t.TempDir().
	direktori, err := os.MkdirTemp("", "filestore-demo-*")
	if err != nil {
		fmt.Println("galat:", err)
		return
	}
	defer os.RemoveAll(direktori)

	store := NewFileStore(direktori + "/pelanggan.json")

	p, err := store.Create("Budi", "budi@example.com")
	if err != nil {
		fmt.Println("galat:", err)
		return
	}
	fmt.Printf("buat  : ID=%d %s <%s>\n", p.ID, p.Nama, p.Email)

	if _, err := store.Create("Siti", "siti@example.com"); err != nil {
		fmt.Println("galat:", err)
		return
	}
	daftar, _ := store.List()
	for _, pl := range daftar {
		fmt.Printf("list  : ID=%d %s <%s>\n", pl.ID, pl.Nama, pl.Email)
	}

	if err := store.Delete(p.ID); err != nil {
		fmt.Println("galat:", err)
		return
	}
	fmt.Println("hapus : OK")
	if _, err := store.Get(p.ID); err != nil {
		fmt.Println("get(1):", err)
	}
}
