package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"
	"time"
)

// Pengguna adalah representasi resource yang dikirim ke klien.
type Pengguna struct {
	ID        int       `json:"id"`
	Email     string    `json:"email"`
	Name      string    `json:"name"`
	CreatedAt time.Time `json:"created_at"`
}

// masukanPengguna adalah payload pembuatan pengguna.
type masukanPengguna struct {
	Email string  `json:"email"`
	Name  *string `json:"name"`
}

// Handler menyimpan pengguna beserta indeks email untuk deteksi duplikat.
type Handler struct {
	data     []Pengguna
	emailAda map[string]bool
	waktuDib time.Time
}

func (h *Handler) routes() *http.ServeMux {
	mux := http.NewServeMux()
	mux.HandleFunc("POST /users", h.buat)
	mux.HandleFunc("GET /users/{id}", h.detail)
	return mux
}

func (h *Handler) buat(w http.ResponseWriter, r *http.Request) {
	var in masukanPengguna
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		tulisGalat(w, http.StatusBadRequest, "INVALID_JSON", "body bukan JSON yang valid")
		return
	}
	if in.Name == nil || strings.TrimSpace(*in.Name) == "" {
		tulisGalat(w, http.StatusBadRequest, "VALIDATION_ERROR", "name wajib diisi")
		return
	}
	if h.emailAda[in.Email] {
		tulisGalat(w, http.StatusConflict, "EMAIL_TELAH_DIPAKAI",
			fmt.Sprintf("email %s sudah terdaftar", in.Email))
		return
	}

	u := Pengguna{
		ID:        len(h.data) + 1,
		Email:     in.Email,
		Name:      strings.TrimSpace(*in.Name),
		CreatedAt: h.waktuDib,
	}
	h.data = append(h.data, u)
	h.emailAda[in.Email] = true
	w.Header().Set("Location", fmt.Sprintf("/users/%d", u.ID))
	tulisJSON(w, http.StatusCreated, u)
}

func (h *Handler) detail(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.Atoi(r.PathValue("id"))
	if err != nil {
		tulisGalat(w, http.StatusBadRequest, "INVALID_ID", "id harus angka")
		return
	}
	for _, u := range h.data {
		if u.ID == id {
			tulisJSON(w, http.StatusOK, u)
			return
		}
	}
	tulisGalat(w, http.StatusNotFound, "NOT_FOUND",
		fmt.Sprintf("pengguna dengan id %d tidak ditemukan", id))
}

type galatBody struct {
	Error galatRincian `json:"error"`
}

type galatRincian struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

func tulisGalat(w http.ResponseWriter, status int, kode, pesan string) {
	tulisJSON(w, status, galatBody{Error: galatRincian{Code: kode, Message: pesan}})
}

func tulisJSON(w http.ResponseWriter, status int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(v)
}

func main() {
	h := &Handler{
		emailAda: make(map[string]bool),
		waktuDib: time.Date(2026, 1, 15, 8, 0, 0, 0, time.UTC),
	}
	mux := h.routes()

	// 201: sumber daya baru berhasil dibuat, Location menunjuk padanya.
	kirim(mux, "POST", "/users", `{"email":"budi@example.com","name":"Budi"}`)

	// 409: email sudah terdaftar — konflik duplikat.
	kirim(mux, "POST", "/users", `{"email":"budi@example.com","name":"Budi Kedua"}`)

	// 400: payload JSON sah tetapi field name hilang.
	kirim(mux, "POST", "/users", `{"email":"siti@example.com"}`)

	// 200: pembacaan berhasil.
	kirim(mux, "GET", "/users/1", "")

	// 404: sumber daya tidak ada.
	kirim(mux, "GET", "/users/99", "")
}

// kirim mengeksekusi satu permintaan lalu mencetak dump status, Location, dan body.
func kirim(mux *http.ServeMux, metode, target, body string) {
	var pembaca io.Reader
	if body != "" {
		pembaca = strings.NewReader(body)
	}
	req := httptest.NewRequest(metode, target, pembaca)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	fmt.Printf("%-5s %-11s -> %d", metode, target, rec.Code)
	if loc := rec.Header().Get("Location"); loc != "" {
		fmt.Printf(" (Location: %s)", loc)
	}
	fmt.Println()
	if rec.Body.Len() > 0 {
		fmt.Printf("   %s", rec.Body.String())
	}
}
