import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class UjiIrisan {

    record DtoProduk(String nama, long harga) { }

    record Produk(long id, String nama, long harga) { }

    record Respons(int status, String isi) { }

    // ==== Kontrak lapisan: irisan web menyuntik pengganti (padanan @MockitoBean) ====
    interface RepoProduk {

        Produk tambah(DtoProduk dto);

        Optional<Produk> cari(long id);

        List<Produk> semua();
    }

    interface LayananProduk {

        List<Produk> semua();

        Optional<Produk> cari(long id);
    }

    // ==== Implementasi nyata ====
    static class RepoMemori implements RepoProduk {

        private final Map<Long, Produk> tabel = new LinkedHashMap<>();
        private long urut = 0;

        @Override
        public Produk tambah(DtoProduk dto) {
            Produk p = new Produk(++urut, dto.nama(), dto.harga());
            tabel.put(p.id(), p);
            return p;
        }

        @Override
        public Optional<Produk> cari(long id) {
            return Optional.ofNullable(tabel.get(id));
        }

        @Override
        public List<Produk> semua() {
            return List.copyOf(tabel.values());
        }
    }

    static class LayananNyata implements LayananProduk {

        private final RepoProduk repo;

        LayananNyata(RepoProduk repo) {
            this.repo = repo;
        }

        @Override
        public List<Produk> semua() {
            return repo.semua();
        }

        @Override
        public Optional<Produk> cari(long id) {
            return repo.cari(id);
        }
    }

    // Stub tetap: padanan @MockitoBean pada irisan web.
    static class LayananPalsu implements LayananProduk {

        private final List<Produk> tetap = List.of(
                new Produk(1, "Kopi Gayo 250g", 85_000),
                new Produk(2, "Teh Melati 100g", 45_000));

        @Override
        public List<Produk> semua() {
            return tetap;
        }

        @Override
        public Optional<Produk> cari(long id) {
            return tetap.stream().filter(p -> p.id() == id).findFirst();
        }
    }

    // ==== Controller: validasi input sebagai padanan @Valid ====
    static class KontrolProduk {

        private final LayananProduk layanan;

        KontrolProduk(LayananProduk layanan) {
            this.layanan = layanan;
        }

        Respons semua() {
            return new Respons(200, layanan.semua().size() + " produk");
        }

        Respons satu(long id) {
            return layanan.cari(id).isPresent()
                    ? new Respons(200, "produk " + id)
                    : new Respons(404, "tidak ditemukan");
        }

        Respons buat(DtoProduk dto) {
            // Validasi terjadi SEBELUM service dipanggil (padanan @Valid).
            if (dto.nama() == null || dto.nama().isBlank() || dto.harga() <= 0) {
                return new Respons(400, "validasi gagal");
            }
            return new Respons(201, "dibuat");
        }
    }

    // ==== MockMvc mini: perform request tanpa server nyata ====
    static Respons perform(KontrolProduk k, String method, String path, DtoProduk dto) {
        if ("GET".equals(method) && path.equals("/api/produk")) {
            return k.semua();
        }
        if ("GET".equals(method) && path.startsWith("/api/produk/")) {
            return k.satu(Long.parseLong(path.substring("/api/produk/".length())));
        }
        if ("POST".equals(method)) {
            return k.buat(dto);
        }
        throw new IllegalArgumentException(method + " " + path);
    }

    static int lolos = 0;
    static int total = 0;

    static void uji(String label, Object ekspektasi, Object aktual) {
        total++;
        boolean ok = ekspektasi.equals(aktual);
        if (ok) {
            lolos++;
        }
        System.out.printf("  %s %-34s ekspektasi=%s aktual=%s%n",
                ok ? "PASS" : "GAGAL", label, ekspektasi, aktual);
    }

    public static void main(String[] args) {
        System.out.println("== @SpringBootTest: konteks penuh ==");
        RepoMemori repo = new RepoMemori();
        repo.tambah(new DtoProduk("Kopi Gayo 250g", 85_000));
        repo.tambah(new DtoProduk("Teh Melati 100g", 45_000));
        LayananNyata layanan = new LayananNyata(repo);
        KontrolProduk kontrol = new KontrolProduk(layanan);
        System.out.println("  [ctx] repoProduk     -> RepoMemori");
        System.out.println("  [ctx] layananProduk  -> LayananNyata");
        System.out.println("  [ctx] kontrolProduk  -> KontrolProduk");
        System.out.println("  bean termuat: 3");
        uji("layanan konteks penuh", 2, layanan.semua().size());
        System.out.println();

        System.out.println("== @WebMvcTest: irisan web ==");
        KontrolProduk kontrolWeb = new KontrolProduk(new LayananPalsu());
        System.out.println("  [ctx] kontrolProduk  -> KontrolProduk");
        System.out.println("  [ctx] layananProduk  -> LayananPalsu (padanan @MockitoBean)");
        System.out.println("  bean termuat: 2 (RepoMemori tidak disentuh)");
        System.out.println();

        System.out.println("== @DataJpaTest: irisan data ==");
        RepoMemori repoUji = new RepoMemori();
        repoUji.tambah(new DtoProduk("Gula 1kg", 16_000));
        System.out.println("  [ctx] repoProduk     -> RepoMemori (penyimpanan in-memory)");
        System.out.println("  bean termuat: 1 (controller tidak termuat)");
        uji("repo cari(1)", true, repoUji.cari(1).isPresent());
        uji("repo cari(99)", false, repoUji.cari(99).isPresent());
        System.out.println();

        System.out.println("== MockMvc mini pada irisan web ==");
        Respons daftar = perform(kontrolWeb, "GET", "/api/produk", null);
        uji("status GET /api/produk", 200, daftar.status());
        uji("isi GET /api/produk", "2 produk", daftar.isi());

        Respons hilang = perform(kontrolWeb, "GET", "/api/produk/99", null);
        uji("status GET /api/produk/99", 404, hilang.status());

        Respons tolak = perform(kontrolWeb, "POST", "/api/produk",
                new DtoProduk("", -1));
        uji("status POST validasi", 400, tolak.status());

        System.out.println();
        System.out.println("ringkasan: " + lolos + "/" + total + " assertion lolos");
    }
}
