package main

import (
	"encoding/json"
	"fmt"
	"io"
	"maps"
	"net/http"
	"net/http/httptest"
	"slices"
	"strings"
)

// Endpoint merangkum satu rute hasil rancangan API tugas.
type Endpoint struct {
	Metode    string
	Path      string
	Deskripsi string
}

func main() {
	// Rancangan endpoint: URL memakai noun resource, bukan verb.
	rancangan := []Endpoint{
		{http.MethodGet, "/todos", "daftar seluruh tugas"},
		{http.MethodPost, "/todos", "buat tugas baru"},
		{http.MethodGet, "/todos/{id}", "baca satu tugas"},
		{http.MethodPut, "/todos/{id}", "ganti seluruh isi tugas"},
		{http.MethodDelete, "/todos/{id}", "hapus satu tugas"},
		{http.MethodGet, "/users/{id}/orders", "baca pesanan milik satu pengguna"},
	}

	fmt.Println("Rancangan endpoint API tugas:")
	for _, e := range rancangan {
		fmt.Printf("  %-7s %-19s %s\n", e.Metode, e.Path, e.Deskripsi)
	}
	fmt.Println()

	// State di memori untuk membuktikan efek tiap method terhadap resource.
	data := make(map[int]string)
	mux := http.NewServeMux()

	mux.HandleFunc("POST /todos", func(w http.ResponseWriter, r *http.Request) {
		var masukan struct {
			Title string `json:"title"`
		}
		if err := json.NewDecoder(r.Body).Decode(&masukan); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		data[len(data)+1] = masukan.Title
		w.WriteHeader(http.StatusCreated)
	})

	mux.HandleFunc("PUT /todos/{id}", func(w http.ResponseWriter, r *http.Request) {
		var masukan struct {
			Title string `json:"title"`
		}
		if err := json.NewDecoder(r.Body).Decode(&masukan); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		data[7] = masukan.Title // disederhanakan: contoh mengunci id 7
		w.WriteHeader(http.StatusOK)
	})

	mux.HandleFunc("DELETE /todos/{id}", func(w http.ResponseWriter, r *http.Request) {
		delete(data, 7) // disederhanakan: contoh mengunci id 7
		w.WriteHeader(http.StatusNoContent)
	})

	kirim := func(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("  %s %s -> %d ; data = %s\n", metode, target, rec.Code, isiData(data))
	}

	fmt.Println("POST /todos dua kali dengan body yang sama:")
	kirim("POST", "/todos", `{"title":"Belajar Go"}`)
	kirim("POST", "/todos", `{"title":"Belajar Go"}`)
	fmt.Println("  POST tidak idempotent: jumlah data bertambah pada panggilan kedua.")
	fmt.Println()

	fmt.Println("PUT /todos/7 dua kali dengan body yang sama:")
	kirim("PUT", "/todos/7", `{"title":"Rapat tim"}`)
	kirim("PUT", "/todos/7", `{"title":"Rapat tim"}`)
	fmt.Println("  PUT idempotent: state setelah panggilan kedua tidak berubah.")
	fmt.Println()

	fmt.Println("DELETE /todos/7 dua kali:")
	kirim("DELETE", "/todos/7", "")
	kirim("DELETE", "/todos/7", "")
	fmt.Println("  DELETE idempotent: state tetap sama dan respons tetap 204.")
}

// isiData merangkai isi store menjadi teks dengan kunci terurut.
func isiData(data map[int]string) string {
	bagian := make([]string, 0, len(data))
	for _, kunci := range slices.Sorted(maps.Keys(data)) {
		bagian = append(bagian, fmt.Sprintf("%d:%s", kunci, data[kunci]))
	}
	return "[" + strings.Join(bagian, ", ") + "]"
}
