package main

import (
	"errors"
	"fmt"
)

// Pengguna adalah data domain milik modul high-level.
type Pengguna struct {
	ID    int
	Email string
}

// UserRepository adalah abstraksi penyimpanan yang dibutuhkan layanan.
type UserRepository interface {
	Simpan(p Pengguna) error
	Cari(id int) (Pengguna, error)
}

// MemoryRepository adalah implementasi low-level berbasis map.
type MemoryRepository struct {
	data map[int]Pengguna
}

func NewMemoryRepository() *MemoryRepository {
	return &MemoryRepository{data: make(map[int]Pengguna)}
}

func (m *MemoryRepository) Simpan(p Pengguna) error {
	m.data[p.ID] = p
	return nil
}

func (m *MemoryRepository) Cari(id int) (Pengguna, error) {
	p, ok := m.data[id]
	if !ok {
		return Pengguna{}, fmt.Errorf("pengguna %d tidak ditemukan", id)
	}
	return p, nil
}

// LayananPengguna adalah modul high-level: hanya mengenal abstraksi.
type LayananPengguna struct {
	repo UserRepository
}

func (s *LayananPengguna) Daftarkan(id int, email string) error {
	if id <= 0 {
		return errors.New("id harus positif")
	}
	return s.repo.Simpan(Pengguna{ID: id, Email: email})
}

func (s *LayananPengguna) AmbilEmail(id int) (string, error) {
	p, err := s.repo.Cari(id)
	if err != nil {
		return "", err
	}
	return p.Email, nil
}

// fakeRepo adalah pengganti uji: mencatat pemanggilan tanpa menyimpan data.
type fakeRepo struct {
	terpanggil int
}

func (f *fakeRepo) Simpan(p Pengguna) error {
	f.terpanggil++
	return nil
}

func (f *fakeRepo) Cari(id int) (Pengguna, error) {
	f.terpanggil++
	return Pengguna{ID: id, Email: "test@example.com"}, nil
}

func main() {
	// Jalur produksi: layanan berjalan di atas repository memori.
	layanan := &LayananPengguna{repo: NewMemoryRepository()}

	if err := layanan.Daftarkan(0, "x@example.com"); err != nil {
		fmt.Println("validasi menolak:", err)
	}
	if err := layanan.Daftarkan(7, "budi@example.com"); err != nil {
		fmt.Println("gagal menyimpan:", err)
	}
	email, err := layanan.AmbilEmail(7)
	fmt.Println("email:", email, "err:", err)

	// Jalur pengujian: layanan yang sama diuji dengan fakeRepo.
	fake := &fakeRepo{}
	layananUji := &LayananPengguna{repo: fake}
	_ = layananUji.Daftarkan(1, "test@example.com")
	emailUji, _ := layananUji.AmbilEmail(1)
	fmt.Println("email dari fake:", emailUji)
	fmt.Println("jumlah pemanggilan repo:", fake.terpanggil)
}
