package commerce

import (
	"encoding/json"
	"errors"
	"log/slog"
	"net/http"
	"strconv"
	"strings"

	"github.com/go-chi/chi/v5"

	qmiddleware "qycw/go-server/internal/middleware"
	"qycw/go-server/internal/response"
)

type NotificationHandler struct {
	service *Service
	auth    *qmiddleware.Auth
	logger  *slog.Logger
}

func NewNotificationHandler(service *Service, auth *qmiddleware.Auth, logger *slog.Logger) *NotificationHandler {
	return &NotificationHandler{service: service, auth: auth, logger: logger}
}

func (h *NotificationHandler) Routes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireUser)
	r.Get("/unread-count", h.unreadCount)
	r.Get("/announcements", h.announcements)
	r.Post("/announcements/{announcement_id}/read", h.readAnnouncement)
	r.Get("/preferences", h.preferences)
	r.Put("/preferences", h.updatePreferences)
	r.Post("/read-all", h.readAll)
	r.Get("/", h.list)
	r.Post("/batch/read", h.batchRead)
	r.Post("/batch/delete", h.batchDelete)
	r.Post("/batch/archive", h.batchArchive)
	r.Post("/batch/handled", h.batchHandled)
	r.Post("/{notification_id}/read", h.readOne)
	r.Delete("/{notification_id}", h.deleteOne)
	return r
}

func (h *NotificationHandler) list(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	query := r.URL.Query()
	limit := queryInt(query.Get("limit"), 20, 1, 100)
	offset := queryInt(query.Get("offset"), 0, 0, 100000)
	data, err := h.service.ListNotifications(r.Context(), userID, NotificationQuery{
		Category:      strings.TrimSpace(query.Get("category")),
		Level:         strings.TrimSpace(query.Get("level")),
		UnreadOnly:    queryBool(query.Get("unread_only")),
		ImportantOnly: queryBool(query.Get("important_only")),
		ArchivedOnly:  queryBool(query.Get("archived_only")),
		HandledState:  strings.TrimSpace(query.Get("handled_state")),
		Limit:         limit,
		Offset:        offset,
	})
	h.writeResult(w, data, err)
}

func (h *NotificationHandler) unreadCount(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	data, err := h.service.NotificationUnreadCount(r.Context(), userID)
	h.writeResult(w, data, err)
}

func (h *NotificationHandler) announcements(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	limit := queryInt(r.URL.Query().Get("limit"), 20, 1, 100)
	offset := queryInt(r.URL.Query().Get("offset"), 0, 0, 100000)
	data, err := h.service.ListAnnouncements(r.Context(), userID, limit, offset)
	h.writeResult(w, data, err)
}

func (h *NotificationHandler) preferences(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	data, err := h.service.NotificationPreferences(r.Context(), userID)
	h.writeResult(w, data, err)
}

func (h *NotificationHandler) updatePreferences(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	var req NotificationPreferenceUpdateRequest
	if !decodeNotificationJSON(w, r, &req) {
		return
	}
	data, err := h.service.UpdateNotificationPreferences(r.Context(), userID, req.Preferences)
	h.writeResult(w, data, err)
}

func (h *NotificationHandler) readAll(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	data, err := h.service.MarkAllNotificationsRead(r.Context(), userID)
	h.writeResult(w, data, err)
}

func (h *NotificationHandler) readOne(w http.ResponseWriter, r *http.Request) {
	userID, id, ok := notificationUserAndID(w, r, "notification_id")
	if !ok {
		return
	}
	data, err := h.service.MarkNotificationRead(r.Context(), userID, id)
	h.writeResult(w, data, err)
}

func (h *NotificationHandler) deleteOne(w http.ResponseWriter, r *http.Request) {
	userID, id, ok := notificationUserAndID(w, r, "notification_id")
	if !ok {
		return
	}
	data, err := h.service.DeleteNotification(r.Context(), userID, id)
	h.writeResult(w, data, err)
}

func (h *NotificationHandler) readAnnouncement(w http.ResponseWriter, r *http.Request) {
	userID, id, ok := notificationUserAndID(w, r, "announcement_id")
	if !ok {
		return
	}
	data, err := h.service.MarkAnnouncementRead(r.Context(), userID, id)
	h.writeResult(w, data, err)
}

func (h *NotificationHandler) batchRead(w http.ResponseWriter, r *http.Request) {
	h.batchIDs(w, r, func(userID int64, ids []int64) (any, error) {
		return h.service.MarkManyNotificationsRead(r.Context(), userID, ids)
	})
}

func (h *NotificationHandler) batchDelete(w http.ResponseWriter, r *http.Request) {
	h.batchIDs(w, r, func(userID int64, ids []int64) (any, error) {
		return h.service.DeleteManyNotifications(r.Context(), userID, ids)
	})
}

func (h *NotificationHandler) batchArchive(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	var req NotificationArchiveRequest
	if !decodeNotificationJSON(w, r, &req) {
		return
	}
	data, err := h.service.SetManyNotificationsArchived(r.Context(), userID, req.IDs, req.Archived)
	h.writeResult(w, data, err)
}

func (h *NotificationHandler) batchHandled(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	var req NotificationHandledRequest
	if !decodeNotificationJSON(w, r, &req) {
		return
	}
	data, err := h.service.SetManyNotificationsHandled(r.Context(), userID, req.IDs, req.Handled)
	h.writeResult(w, data, err)
}

func (h *NotificationHandler) batchIDs(w http.ResponseWriter, r *http.Request, fn func(int64, []int64) (any, error)) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	var req NotificationBatchRequest
	if !decodeNotificationJSON(w, r, &req) {
		return
	}
	data, err := fn(userID, req.IDs)
	h.writeResult(w, data, err)
}

func (h *NotificationHandler) writeResult(w http.ResponseWriter, data any, err error) {
	if err != nil {
		h.writeError(w, err)
		return
	}
	response.OK(w, data)
}

func (h *NotificationHandler) writeError(w http.ResponseWriter, err error) {
	var httpErr HTTPError
	if errors.As(err, &httpErr) {
		response.Detail(w, httpErr.Status, httpErr.Detail)
		return
	}
	if h.logger != nil {
		h.logger.Error("notification handler error", "error", err)
	}
	response.Detail(w, http.StatusInternalServerError, msgServiceError)
}

func notificationUserAndID(w http.ResponseWriter, r *http.Request, key string) (int64, int64, bool) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return 0, 0, false
	}
	id, ok := parseID(w, chi.URLParam(r, key))
	if !ok {
		return 0, 0, false
	}
	return userID, id, true
}

func notificationUserID(w http.ResponseWriter, r *http.Request) (int64, bool) {
	principal, ok := qmiddleware.PrincipalFromContext(r.Context())
	if !ok || principal.ID <= 0 {
		response.Detail(w, http.StatusUnauthorized, "Could not validate credentials")
		return 0, false
	}
	return principal.ID, true
}

func decodeNotificationJSON(w http.ResponseWriter, r *http.Request, out any) bool {
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(out); err != nil {
		response.Detail(w, http.StatusBadRequest, msgInvalidParams)
		return false
	}
	return true
}

func queryInt(raw string, fallback int, minValue int, maxValue int) int {
	value, err := strconv.Atoi(strings.TrimSpace(raw))
	if err != nil {
		value = fallback
	}
	if value < minValue {
		return minValue
	}
	if value > maxValue {
		return maxValue
	}
	return value
}

func queryBool(raw string) bool {
	switch strings.ToLower(strings.TrimSpace(raw)) {
	case "1", "true", "yes", "on":
		return true
	default:
		return false
	}
}
