package main

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

// Todo adalah representasi resource tugas yang dikirim ke klien.
type Todo struct {
	ID        int       `json:"id"`
	Title     string    `json:"title"`
	Done      bool      `json:"done"`
	CreatedAt time.Time `json:"created_at"`
}

// TodoStore menyimpan data sekaligus aturan aksesnya (lapisan service).
type TodoStore struct {
	waktu time.Time
	data  []Todo
}

func (s *TodoStore) Buat(title string, done bool) Todo {
	t := Todo{
		ID:        len(s.data) + 1,
		Title:     title,
		Done:      done,
		CreatedAt: s.waktu,
	}
	s.data = append(s.data, t)
	return t
}

func (s *TodoStore) Cari(id int) (Todo, bool) {
	for _, t := range s.data {
		if t.ID == id {
			return t, true
		}
	}
	return Todo{}, false
}

func (s *TodoStore) Ganti(id int, title string, done bool) (Todo, bool) {
	for i := range s.data {
		if s.data[i].ID == id {
			s.data[i].Title = title
			s.data[i].Done = done
			return s.data[i], true
		}
	}
	return Todo{}, false
}

func (s *TodoStore) Hapus(id int) bool {
	for i := range s.data {
		if s.data[i].ID == id {
			s.data = append(s.data[:i], s.data[i+1:]...)
			return true
		}
	}
	return false
}

// Handler tipis: parse permintaan, validasi, panggil service, encode respons.
type Handler struct {
	store *TodoStore
}

func (h *Handler) Routes() *http.ServeMux {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /todos", h.daftar)
	mux.HandleFunc("POST /todos", h.buat)
	mux.HandleFunc("GET /todos/{id}", h.detail)
	mux.HandleFunc("PUT /todos/{id}", h.ubah)
	mux.HandleFunc("DELETE /todos/{id}", h.hapus)
	return mux
}

func (h *Handler) daftar(w http.ResponseWriter, r *http.Request) {
	tulisJSON(w, http.StatusOK, h.store.data)
}

func (h *Handler) buat(w http.ResponseWriter, r *http.Request) {
	title, done, ok := h.parseBody(w, r)
	if !ok {
		return
	}
	t := h.store.Buat(title, done)
	w.Header().Set("Location", fmt.Sprintf("/todos/%d", t.ID))
	tulisJSON(w, http.StatusCreated, t)
}

func (h *Handler) detail(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, r)
	if !ok {
		return
	}
	t, ada := h.store.Cari(id)
	if !ada {
		tulisError(w, http.StatusNotFound, "tugas tidak ditemukan")
		return
	}
	tulisJSON(w, http.StatusOK, t)
}

func (h *Handler) ubah(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, r)
	if !ok {
		return
	}
	title, done, ok := h.parseBody(w, r)
	if !ok {
		return
	}
	t, ada := h.store.Ganti(id, title, done)
	if !ada {
		tulisError(w, http.StatusNotFound, "tugas tidak ditemukan")
		return
	}
	tulisJSON(w, http.StatusOK, t)
}

func (h *Handler) hapus(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, r)
	if !ok {
		return
	}
	if !h.store.Hapus(id) {
		tulisError(w, http.StatusNotFound, "tugas tidak ditemukan")
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// parseID membaca segmen {id} dari pattern dan memastikan isinya angka.
func parseID(w http.ResponseWriter, r *http.Request) (int, bool) {
	id, err := strconv.Atoi(r.PathValue("id"))
	if err != nil {
		tulisError(w, http.StatusBadRequest, "id harus angka")
		return 0, false
	}
	return id, true
}

// parseBody decode payload JSON lalu memvalidasi field title.
func (h *Handler) parseBody(w http.ResponseWriter, r *http.Request) (string, bool, bool) {
	var in struct {
		Title *string `json:"title"`
		Done  bool    `json:"done"`
	}
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		tulisError(w, http.StatusBadRequest, "body bukan JSON yang valid")
		return "", false, false
	}
	if in.Title == nil || strings.TrimSpace(*in.Title) == "" {
		tulisError(w, http.StatusBadRequest, "title wajib diisi")
		return "", false, false
	}
	return strings.TrimSpace(*in.Title), in.Done, true
}

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 tulisError(w http.ResponseWriter, status int, pesan string) {
	tulisJSON(w, status, map[string]string{"error": pesan})
}

func main() {
	store := &TodoStore{waktu: time.Date(2026, 1, 15, 8, 0, 0, 0, time.UTC)}
	store.Buat("Belajar routing", false)
	store.Buat("Belajar handler", true)
	mux := (&Handler{store: store}).Routes()

	kirim(mux, "GET", "/todos", "")
	kirim(mux, "POST", "/todos", `{"title":"Belajar REST"}`)
	kirim(mux, "POST", "/todos", `{"title":""}`)
	kirim(mux, "GET", "/todos/99", "")
	kirim(mux, "PUT", "/todos/1", `{"title":"Belajar routing HTTP","done":true}`)
	kirim(mux, "DELETE", "/todos/2", "")
	kirim(mux, "PATCH", "/todos/1", "")
}

// kirim mengeksekusi satu permintaan ke mux lalu mencetak dump status 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)
	if body != "" {
		req.Header.Set("Content-Type", "application/json")
	}
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	fmt.Printf("%-7s %-11s -> %d\n", metode, target, rec.Code)
	if rec.Body.Len() > 0 {
		fmt.Printf("   %s", rec.Body.String())
	}
}
