package main

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"net/http"
	"strings"
	"time"
)

type API struct {
	store *Store
}

func newID() string {
	b := make([]byte, 8)
	_, _ = rand.Read(b)
	return hex.EncodeToString(b)
}

func writeJSON(w http.ResponseWriter, status int, v any) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(v)
}

func writeErr(w http.ResponseWriter, status int, msg string) {
	writeJSON(w, status, map[string]string{"error": msg})
}

// decode читает JSON-тело запроса в dst.
func decode(r *http.Request, dst any) error {
	return json.NewDecoder(r.Body).Decode(dst)
}

// ---- Клубы ----

func (a *API) listClubs(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, a.store.ListClubs())
}

func (a *API) createClub(w http.ResponseWriter, r *http.Request) {
	var in struct {
		Name        string `json:"name"`
		Description string `json:"description"`
	}
	if err := decode(r, &in); err != nil || strings.TrimSpace(in.Name) == "" {
		writeErr(w, http.StatusBadRequest, "нужно указать name")
		return
	}
	writeJSON(w, http.StatusCreated, a.store.CreateClub(in.Name, in.Description))
}

func (a *API) getClub(w http.ResponseWriter, r *http.Request) {
	c, err := a.store.GetClub(r.PathValue("id"))
	if err != nil {
		writeErr(w, http.StatusNotFound, "клуб не найден")
		return
	}
	writeJSON(w, http.StatusOK, c)
}

// ---- Участники ----

func (a *API) listMembers(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, a.store.ListMembers(r.PathValue("id")))
}

func (a *API) joinClub(w http.ResponseWriter, r *http.Request) {
	var in struct {
		Name string `json:"name"`
	}
	if err := decode(r, &in); err != nil || strings.TrimSpace(in.Name) == "" {
		writeErr(w, http.StatusBadRequest, "нужно указать name")
		return
	}
	m, err := a.store.JoinClub(r.PathValue("id"), in.Name)
	if err != nil {
		writeErr(w, http.StatusNotFound, "клуб не найден")
		return
	}
	writeJSON(w, http.StatusCreated, m)
}

// ---- Книги ----

func (a *API) listBooks(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, a.store.ListBooks(r.PathValue("id")))
}

func (a *API) addBook(w http.ResponseWriter, r *http.Request) {
	var in struct {
		Title   string `json:"title"`
		Author  string `json:"author"`
		AddedBy string `json:"addedBy"`
	}
	if err := decode(r, &in); err != nil || strings.TrimSpace(in.Title) == "" {
		writeErr(w, http.StatusBadRequest, "нужно указать title")
		return
	}
	b, err := a.store.AddBook(r.PathValue("id"), in.Title, in.Author, in.AddedBy)
	if err != nil {
		writeErr(w, http.StatusNotFound, "клуб не найден")
		return
	}
	writeJSON(w, http.StatusCreated, b)
}

func (a *API) voteBook(w http.ResponseWriter, r *http.Request) {
	var in struct {
		Voter string `json:"voter"`
	}
	if err := decode(r, &in); err != nil || strings.TrimSpace(in.Voter) == "" {
		writeErr(w, http.StatusBadRequest, "нужно указать voter")
		return
	}
	b, err := a.store.ToggleVote(r.PathValue("bookId"), in.Voter)
	if err != nil {
		writeErr(w, http.StatusNotFound, "книга не найдена")
		return
	}
	writeJSON(w, http.StatusOK, b)
}

func (a *API) setBookStatus(w http.ResponseWriter, r *http.Request) {
	var in struct {
		Status BookStatus `json:"status"`
	}
	if err := decode(r, &in); err != nil {
		writeErr(w, http.StatusBadRequest, "плохой запрос")
		return
	}
	switch in.Status {
	case StatusSuggested, StatusReading, StatusFinished:
	default:
		writeErr(w, http.StatusBadRequest, "недопустимый статус")
		return
	}
	b, err := a.store.SetBookStatus(r.PathValue("bookId"), in.Status)
	if err != nil {
		writeErr(w, http.StatusNotFound, "книга не найдена")
		return
	}
	writeJSON(w, http.StatusOK, b)
}

// ---- Встречи ----

func (a *API) listMeetings(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, a.store.ListMeetings(r.PathValue("id")))
}

func (a *API) createMeeting(w http.ResponseWriter, r *http.Request) {
	var in struct {
		BookID   string `json:"bookId"`
		Title    string `json:"title"`
		StartsAt string `json:"startsAt"` // ISO 8601
		Location string `json:"location"`
		Notes    string `json:"notes"`
	}
	if err := decode(r, &in); err != nil || strings.TrimSpace(in.Title) == "" {
		writeErr(w, http.StatusBadRequest, "нужно указать title")
		return
	}
	startsAt, err := time.Parse(time.RFC3339, in.StartsAt)
	if err != nil {
		writeErr(w, http.StatusBadRequest, "startsAt должен быть в формате RFC3339")
		return
	}
	m, err := a.store.CreateMeeting(r.PathValue("id"), in.BookID, in.Title, startsAt, in.Location, in.Notes)
	if err != nil {
		writeErr(w, http.StatusNotFound, "клуб не найден")
		return
	}
	writeJSON(w, http.StatusCreated, m)
}

// ---- Обсуждение ----

func (a *API) listPosts(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, a.store.ListPosts(r.PathValue("id")))
}

func (a *API) addPost(w http.ResponseWriter, r *http.Request) {
	var in struct {
		Author string `json:"author"`
		Body   string `json:"body"`
	}
	if err := decode(r, &in); err != nil || strings.TrimSpace(in.Body) == "" {
		writeErr(w, http.StatusBadRequest, "нужно указать body")
		return
	}
	p, err := a.store.AddPost(r.PathValue("id"), in.Author, in.Body)
	if err != nil {
		writeErr(w, http.StatusNotFound, "клуб не найден")
		return
	}
	writeJSON(w, http.StatusCreated, p)
}
