import java.util.HashMap;
import java.util.Map;

public class ContohTestDouble {

    record Pengguna(String id, String nama, int umur) {}

    interface RepoPengguna {
        Pengguna cari(String id);

        void simpan(Pengguna pengguna);
    }

    // ---- kode produksi yang bergantung pada repository ----

    static final class LayananPendaftaran {

        private final RepoPengguna repo;

        LayananPendaftaran(RepoPengguna repo) {
            this.repo = repo;
        }

        boolean sudahTerdaftar(String id) {
            return repo.cari(id) != null;
        }

        String daftar(Pengguna pengguna) {
            if (pengguna.umur() < 17) {
                throw new IllegalArgumentException("umur minimal 17: " + pengguna.nama());
            }
            repo.simpan(pengguna);
            return "REG-" + pengguna.id();
        }
    }

    // ---- STUB: jawaban siap pakai untuk query, tanpa verifikasi ----

    static final class StubRepo implements RepoPengguna {

        private final Pengguna jawaban;

        StubRepo(Pengguna jawaban) {
            this.jawaban = jawaban;
        }

        @Override
        public Pengguna cari(String id) {
            return jawaban;
        }

        @Override
        public void simpan(Pengguna pengguna) {
            // tidak relevan untuk skenario query
        }
    }

    // ---- MOCK: merekam interaksi agar bisa diverifikasi ----

    static final class MockRepo implements RepoPengguna {

        private int simpanDipanggil = 0;
        private Pengguna argumenTerakhir;

        @Override
        public Pengguna cari(String id) {
            return null;
        }

        @Override
        public void simpan(Pengguna pengguna) {
            simpanDipanggil++;
            argumenTerakhir = pengguna;
        }

        boolean simpanPernahDipanggil() {
            return simpanDipanggil > 0;
        }

        Pengguna argumenSimpanTerakhir() {
            return argumenTerakhir;
        }
    }

    // ---- FAKE: implementasi ringkas yang benar-benar bekerja ----

    static final class FakeRepo implements RepoPengguna {

        private final Map<String, Pengguna> penyimpanan = new HashMap<>();

        @Override
        public Pengguna cari(String id) {
            return penyimpanan.get(id);
        }

        @Override
        public void simpan(Pengguna pengguna) {
            penyimpanan.put(pengguna.id(), pengguna);
        }

        int jumlahTersimpan() {
            return penyimpanan.size();
        }
    }

    public static void main(String[] args) {
        Pengguna dewi = new Pengguna("P-1", "Dewi", 21);

        System.out.println("== stub: jawaban siap untuk query ==");
        LayananPendaftaran denganStub = new LayananPendaftaran(new StubRepo(dewi));
        System.out.println("sudahTerdaftar(P-1) = " + denganStub.sudahTerdaftar("P-1"));
        LayananPendaftaran denganStubKosong = new LayananPendaftaran(new StubRepo(null));
        System.out.println("sudahTerdaftar(P-2) = " + denganStubKosong.sudahTerdaftar("P-2"));

        System.out.println("== mock: verifikasi interaksi ==");
        MockRepo mock = new MockRepo();
        LayananPendaftaran denganMock = new LayananPendaftaran(mock);
        String id = denganMock.daftar(dewi);
        System.out.println("id pendaftaran    = " + id);
        System.out.println("simpan dipanggil = " + mock.simpanPernahDipanggil());
        System.out.println("argumen terakhir = " + mock.argumenSimpanTerakhir());

        System.out.println("== fake: implementasi ringkas yang bekerja ==");
        FakeRepo fake = new FakeRepo();
        LayananPendaftaran denganFake = new LayananPendaftaran(fake);
        denganFake.daftar(dewi);
        denganFake.daftar(new Pengguna("P-2", "Budi", 19));
        System.out.println("jumlah tersimpan = " + fake.jumlahTersimpan());
        System.out.println("cari(P-2).nama() = " + fake.cari("P-2").nama());
        System.out.println("cari(P-9)        = " + fake.cari("P-9"));
    }
}
