import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ApiToko {

    record Produk(int id, String nama, int harga) {
        String json() {
            return "{\"id\":" + id + ",\"nama\":\"" + nama + "\",\"harga\":" + harga + "}";
        }
    }

    static final Map<Integer, Produk> SIMPANAN = new LinkedHashMap<>();
    static final AtomicInteger ID_BERIKUT = new AtomicInteger(3);

    static {
        SIMPANAN.put(1, new Produk(1, "Buku Java", 85000));
        SIMPANAN.put(2, new Produk(2, "Pena hitam", 5000));
    }

    static void kirim(HttpExchange exchange, int status, String json) throws IOException {
        if (json == null) {
            exchange.sendResponseHeaders(status, -1);
            exchange.close();
            return;
        }
        byte[] byteJson = json.getBytes(StandardCharsets.UTF_8);
        exchange.getResponseHeaders().set("Content-Type", "application/json");
        exchange.sendResponseHeaders(status, byteJson.length);
        try (OutputStream os = exchange.getResponseBody()) {
            os.write(byteJson);
        }
    }

    static String jsonDaftar() {
        StringBuilder b = new StringBuilder("{\"total\":").append(SIMPANAN.size())
                .append(",\"data\":[");
        int i = 0;
        for (Produk p : SIMPANAN.values()) {
            if (i++ > 0) {
                b.append(",");
            }
            b.append(p.json());
        }
        return b.append("]}").toString();
    }

    static void tangani(HttpExchange exchange) throws IOException {
        String metode = exchange.getRequestMethod();
        String path = exchange.getRequestURI().getPath();
        String[] segmen = path.split("/");
        // pola: /produk          -> daftar atau tambah
        // pola: /produk/{id}     -> satu, ubah, atau hapus
        if (segmen.length == 2) {
            switch (metode) {
                case "GET" -> kirim(exchange, 200, jsonDaftar());
                case "POST" -> {
                    String body = new String(exchange.getRequestBody().readAllBytes(),
                            StandardCharsets.UTF_8);
                    Matcher mNama = Pattern.compile("\"nama\"\\s*:\\s*\"([^\"]*)\"").matcher(body);
                    Matcher mHarga = Pattern.compile("\"harga\"\\s*:\\s*(\\d+)").matcher(body);
                    if (!mNama.find() || !mHarga.find()) {
                        kirim(exchange, 400, "{\"kesalahan\":\"nama dan harga wajib ada\"}");
                        return;
                    }
                    int id = ID_BERIKUT.getAndIncrement();
                    Produk baru = new Produk(id, mNama.group(1), Integer.parseInt(mHarga.group(1)));
                    SIMPANAN.put(id, baru);
                    exchange.getResponseHeaders().set("Location", "/produk/" + id);
                    kirim(exchange, 201, baru.json());
                }
                default -> kirim(exchange, 405, "{\"kesalahan\":\"method tidak didukung\"}");
            }
            return;
        }
        int id = Integer.parseInt(segmen[2]);
        Produk p = SIMPANAN.get(id);
        if (p == null) {
            kirim(exchange, 404, "{\"kesalahan\":\"produk " + id + " tidak ditemukan\"}");
            return;
        }
        switch (metode) {
            case "GET" -> kirim(exchange, 200, p.json());
            case "PUT" -> {
                String body = new String(exchange.getRequestBody().readAllBytes(),
                        StandardCharsets.UTF_8);
                Matcher mNama = Pattern.compile("\"nama\"\\s*:\\s*\"([^\"]*)\"").matcher(body);
                Matcher mHarga = Pattern.compile("\"harga\"\\s*:\\s*(\\d+)").matcher(body);
                Produk ubah = new Produk(id,
                        mNama.find() ? mNama.group(1) : p.nama(),
                        mHarga.find() ? Integer.parseInt(mHarga.group(1)) : p.harga());
                SIMPANAN.put(id, ubah);
                kirim(exchange, 200, ubah.json());
            }
            case "DELETE" -> {
                SIMPANAN.remove(id);
                kirim(exchange, 204, null);
            }
            default -> kirim(exchange, 405, "{\"kesalahan\":\"method tidak didukung\"}");
        }
    }

    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
        server.createContext("/produk", ApiToko::tangani);
        server.start();

        String dasar = "http://127.0.0.1:" + server.getAddress().getPort();
        HttpClient klien = HttpClient.newHttpClient();
        try {
            kirimCetak(klien, dasar, "GET", "/produk", null);
            kirimCetak(klien, dasar, "GET", "/produk/2", null);
            kirimCetak(klien, dasar, "GET", "/produk/99", null);
            kirimCetak(klien, dasar, "POST", "/produk",
                    "{\"nama\":\"Penggaris\",\"harga\":12000}");
            kirimCetak(klien, dasar, "PUT", "/produk/3",
                    "{\"nama\":\"Penggaris baja\",\"harga\":15000}");
            kirimCetak(klien, dasar, "DELETE", "/produk/1", null);
            kirimCetak(klien, dasar, "DELETE", "/produk/1", null);
            kirimCetak(klien, dasar, "GET", "/produk", null);
        } finally {
            server.stop(0);
        }
    }

    static void kirimCetak(HttpClient klien, String dasar, String metode,
                           String path, String body) throws Exception {
        HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(dasar + path));
        switch (metode) {
            case "GET" -> builder.GET();
            case "DELETE" -> builder.DELETE();
            default -> builder.method(metode,
                    HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
                    .header("Content-Type", "application/json");
        }
        HttpResponse<String> res = klien.send(builder.build(),
                HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
        String lokasi = res.headers().firstValue("Location")
                .map(l -> "  Location: " + l).orElse("");
        String isi = res.statusCode() == 204 ? "(tanpa body)" : res.body();
        System.out.printf("%-7s %-12s -> %d%s%n", metode, path, res.statusCode(), lokasi);
        System.out.println("   " + isi);
    }
}
