package builtin

// biz_store.go şirket yönetimi tool'larının (biz.*) kalıcı deposudur.
//
// Tasarım:
//   - Koleksiyon → kayıt listesi; her kayıt düz bir map (id, createdAt, updatedAt + alanlar).
//   - Kayıtlar yerinde DEĞİŞTİRİLMEZ (kopyala-yaz): okuyucular kilit tutmadan tutarlı anlık görüntü alır.
//   - Yazma: önce yeni durum bellekte kurulur, SONRA dosyaya atomik yazılır (geçici dosya + rename);
//     dosya yazılamazsa bellek durumu da DEĞİŞMEZ (bellek ile disk ayrışmaz).
//   - path boşsa yalnızca bellekte tutulur (test / deneme).
//   - Bozuk dosya SESSİZCE ezilmez: açılışta hata verilir (veri kaybını önlemek için).

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

type bizStore struct {
	mu   sync.Mutex
	path string
	data map[string][]map[string]any
}

func newBizStore(path string) (*bizStore, error) {
	s := &bizStore{path: strings.TrimSpace(path), data: map[string][]map[string]any{}}
	if s.path == "" {
		return s, nil
	}
	raw, err := os.ReadFile(s.path)
	if errors.Is(err, os.ErrNotExist) {
		return s, nil
	}
	if err != nil {
		return nil, fmt.Errorf("şirket verisi okunamadı (%s): %w", s.path, err)
	}
	if strings.TrimSpace(string(raw)) == "" {
		return s, nil
	}
	if err := json.Unmarshal(raw, &s.data); err != nil {
		return nil, fmt.Errorf("şirket verisi bozuk (%s): %w — dosyayı yedekleyip düzelt ya da kaldır", s.path, err)
	}
	if s.data == nil {
		s.data = map[string][]map[string]any{}
	}
	return s, nil
}

// read koleksiyonun anlık görüntüsünü verir. SALT OKUNUR: dönen kayıtlar değiştirilmemelidir.
func (s *bizStore) read(col string) []map[string]any {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.data[col]
}

// mutate koleksiyonu kilit altında dönüştürür; fn yeni listeyi döndürür (eskisini DEĞİŞTİRMEDEN).
func (s *bizStore) mutate(col string, fn func(recs []map[string]any) ([]map[string]any, error)) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	next, err := fn(s.data[col])
	if err != nil {
		return err
	}
	all := make(map[string][]map[string]any, len(s.data)+1)
	for k, v := range s.data {
		all[k] = v
	}
	all[col] = next
	if err := s.persist(all); err != nil {
		return fmt.Errorf("kayıt dosyaya yazılamadı: %w", err)
	}
	s.data = all
	return nil
}

func (s *bizStore) persist(all map[string][]map[string]any) error {
	if s.path == "" {
		return nil
	}
	raw, err := json.MarshalIndent(all, "", "  ")
	if err != nil {
		return err
	}
	if err := os.MkdirAll(filepath.Dir(s.path), 0o700); err != nil {
		return err
	}
	tmp := s.path + ".tmp"
	if err := os.WriteFile(tmp, raw, 0o600); err != nil {
		return err
	}
	if err := os.Rename(tmp, s.path); err != nil {
		_ = os.Remove(tmp)
		return err
	}
	return nil
}

// newBizID "<önek>_<16 hex>" biçiminde tahmin edilemez kimlik üretir.
func newBizID(prefix string) string {
	b := make([]byte, 8)
	if _, err := rand.Read(b); err != nil {
		// crypto/rand çökerse bile benzersizlik için zaman damgası yedeği yetmez; açık hata ver.
		panic("biz: rastgele kimlik üretilemedi: " + err.Error())
	}
	return prefix + "_" + hex.EncodeToString(b)
}

func cloneRec(r map[string]any) map[string]any {
	out := make(map[string]any, len(r))
	for k, v := range r {
		out[k] = v
	}
	return out
}
