package httpapi

import (
	"log"
	"net/http"
	"strconv"
	"time"

	"git.azzamo.net/Michilis/Spanglish/photo-api/internal/auth"
	"git.azzamo.net/Michilis/Spanglish/photo-api/internal/store"
)

func validVisibility(v string) bool {
	switch v {
	case store.VisibilityPublic, store.VisibilityPrivate, store.VisibilityLink, store.VisibilityTicket:
		return true
	}
	return false
}

type createGalleryBody struct {
	Title         string `json:"title"`
	TitleEs       string `json:"titleEs"`
	Description   string `json:"description"`
	DescriptionEs string `json:"descriptionEs"`
	EventID       string `json:"eventId"`
	Visibility    string `json:"visibility"`
}

func (s *Server) createGallery(w http.ResponseWriter, r *http.Request, user auth.User) {
	var body createGalleryBody
	if err := decodeJSON(r, &body); err != nil {
		writeError(w, http.StatusBadRequest, "Invalid JSON body")
		return
	}
	if body.Title == "" {
		writeError(w, http.StatusBadRequest, "title is required")
		return
	}
	if body.Visibility == "" {
		body.Visibility = store.VisibilityPrivate
	}
	if !validVisibility(body.Visibility) {
		writeError(w, http.StatusBadRequest, "visibility must be public, private, link or ticket")
		return
	}
	if body.EventID != "" {
		if _, err := s.db.GetEventSummary(r.Context(), body.EventID); err != nil {
			writeStoreError(w, err, "Event not found")
			return
		}
	}

	slug, err := s.uniqueSlug(r.Context(), body.Title)
	if err != nil {
		writeStoreError(w, err, "")
		return
	}
	now := time.Now()
	g := store.Gallery{
		ID:            newID(),
		Slug:          slug,
		Title:         body.Title,
		TitleEs:       body.TitleEs,
		Description:   body.Description,
		DescriptionEs: body.DescriptionEs,
		EventID:       body.EventID,
		Visibility:    body.Visibility,
		ShareToken:    newShareToken(),
		CreatedBy:     user.ID,
		CreatedAt:     now,
		UpdatedAt:     now,
	}
	if err := s.db.CreateGallery(r.Context(), g); err != nil {
		writeStoreError(w, err, "")
		return
	}
	writeJSON(w, http.StatusCreated, map[string]any{
		"gallery": s.galleryToJSON(r.Context(), g, s.viewTokenFor(g), true),
	})
}

func (s *Server) listGalleries(w http.ResponseWriter, r *http.Request, _ auth.User) {
	limit := queryInt(r, "limit", 100)
	offset := queryInt(r, "offset", 0)
	galleries, err := s.db.ListGalleries(r.Context(), r.URL.Query().Get("eventId"), limit, offset)
	if err != nil {
		writeStoreError(w, err, "")
		return
	}
	out := make([]galleryJSON, 0, len(galleries))
	for _, g := range galleries {
		out = append(out, s.galleryToJSON(r.Context(), g, s.viewTokenFor(g), true))
	}
	writeJSON(w, http.StatusOK, map[string]any{"galleries": out})
}

func (s *Server) getGallery(w http.ResponseWriter, r *http.Request, _ auth.User) {
	g, err := s.db.GetGallery(r.Context(), r.PathValue("id"))
	if err != nil {
		writeStoreError(w, err, "Gallery not found")
		return
	}
	photos, err := s.db.ListPhotos(r.Context(), g.ID, false)
	if err != nil {
		writeStoreError(w, err, "")
		return
	}
	s.fillPreviewSizes(r.Context(), photos)
	urlToken := s.viewTokenFor(g)
	out := make([]photoJSON, 0, len(photos))
	for _, p := range photos {
		out = append(out, s.photoToJSON(p, urlToken, true))
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"gallery": s.galleryToJSON(r.Context(), g, urlToken, true),
		"photos":  out,
	})
}

type updateGalleryBody struct {
	Title         *string `json:"title"`
	TitleEs       *string `json:"titleEs"`
	Description   *string `json:"description"`
	DescriptionEs *string `json:"descriptionEs"`
	EventID       *string `json:"eventId"`
	Visibility    *string `json:"visibility"`
	CoverPhotoID  *string `json:"coverPhotoId"`
}

func (s *Server) updateGallery(w http.ResponseWriter, r *http.Request, _ auth.User) {
	id := r.PathValue("id")
	var body updateGalleryBody
	if err := decodeJSON(r, &body); err != nil {
		writeError(w, http.StatusBadRequest, "Invalid JSON body")
		return
	}
	if body.Title != nil && *body.Title == "" {
		writeError(w, http.StatusBadRequest, "title cannot be empty")
		return
	}
	if body.Visibility != nil && !validVisibility(*body.Visibility) {
		writeError(w, http.StatusBadRequest, "visibility must be public, private, link or ticket")
		return
	}
	if body.EventID != nil && *body.EventID != "" {
		if _, err := s.db.GetEventSummary(r.Context(), *body.EventID); err != nil {
			writeStoreError(w, err, "Event not found")
			return
		}
	}
	if body.CoverPhotoID != nil && *body.CoverPhotoID != "" {
		p, err := s.db.GetPhoto(r.Context(), *body.CoverPhotoID)
		if err != nil || p.GalleryID != id {
			writeError(w, http.StatusBadRequest, "coverPhotoId must be a photo of this gallery")
			return
		}
	}

	err := s.db.UpdateGallery(r.Context(), id, store.GalleryUpdate{
		Title:         body.Title,
		TitleEs:       body.TitleEs,
		Description:   body.Description,
		DescriptionEs: body.DescriptionEs,
		EventID:       body.EventID,
		Visibility:    body.Visibility,
		CoverPhotoID:  body.CoverPhotoID,
	})
	if err != nil {
		writeStoreError(w, err, "Gallery not found")
		return
	}
	g, err := s.db.GetGallery(r.Context(), id)
	if err != nil {
		writeStoreError(w, err, "Gallery not found")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"gallery": s.galleryToJSON(r.Context(), g, s.viewTokenFor(g), true),
	})
}

func (s *Server) deleteGallery(w http.ResponseWriter, r *http.Request, _ auth.User) {
	id := r.PathValue("id")
	keys, err := s.db.PhotoKeys(r.Context(), id)
	if err != nil {
		writeStoreError(w, err, "")
		return
	}
	if err := s.db.DeleteGallery(r.Context(), id); err != nil {
		writeStoreError(w, err, "Gallery not found")
		return
	}
	// Object cleanup is best-effort after the rows are gone; orphaned
	// objects are harmless (unreachable) and logged for manual sweep.
	for _, key := range keys {
		if err := s.storage.Delete(r.Context(), key); err != nil {
			log.Printf("delete object %s: %v", key, err)
		}
	}
	writeJSON(w, http.StatusOK, map[string]string{"message": "Gallery deleted"})
}

func (s *Server) rotateShareToken(w http.ResponseWriter, r *http.Request, _ auth.User) {
	id := r.PathValue("id")
	if err := s.db.RotateShareToken(r.Context(), id, newShareToken()); err != nil {
		writeStoreError(w, err, "Gallery not found")
		return
	}
	g, err := s.db.GetGallery(r.Context(), id)
	if err != nil {
		writeStoreError(w, err, "Gallery not found")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"gallery": s.galleryToJSON(r.Context(), g, s.viewTokenFor(g), true),
	})
}

func queryInt(r *http.Request, key string, def int) int {
	if v := r.URL.Query().Get(key); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n >= 0 {
			return n
		}
	}
	return def
}
