package User

import (
	"net/http"
	"strconv"

	"astrology-api/helpers"
	"astrology-api/middleware"
	"astrology-api/notifications"

	"github.com/gin-gonic/gin"
)

///////////////////////////////////////////////////////////
// Notification Controller
///////////////////////////////////////////////////////////
//
// The in-app notification centre for the customer app and the customer side of
// the web app. The astrologer stack has its own controller over the same
// notifications.Center, because the two stacks do not share controllers - but
// the request and response shapes are deliberately identical, so the web app
// can render one screen against whichever account is logged in.
//
// Every endpoint scopes to the account in the token. No id in a request body is
// trusted on its own: the centre filters by userId as well, so posting somebody
// else's notification id simply affects nothing.

type NotificationController struct {
	center *notifications.Center
}

func NewNotificationController(center *notifications.Center) *NotificationController {

	return &NotificationController{center: center}
}

///////////////////////////////////////////////////////////
// Requests
///////////////////////////////////////////////////////////

// NotificationIDsRequest is what mark-read and delete take.
//
// `ids` carries one id or many - the app's swipe-to-delete and its multi-select
// post the same shape - and mark-read also accepts `all` for "mark everything
// read", so the app does not have to page the whole list to clear a badge.
type NotificationIDsRequest struct {
	IDs []uint `json:"ids"`
	All bool   `json:"all"`
}

///////////////////////////////////////////////////////////
// GET /api/user/notifications
///////////////////////////////////////////////////////////

// List returns one page of notifications, newest first.
//
// Query: page, limit, status (ALL | READ | UNREAD), search.
func (c *NotificationController) List(ctx *gin.Context) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {

		ctx.JSON(http.StatusUnauthorized, helpers.ErrorResponse("unauthorized"))

		return
	}

	response, err := c.center.List(userID, notificationQuery(ctx))

	if err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	ctx.JSON(
		http.StatusOK,
		helpers.SuccessResponse("Notifications fetched successfully", response),
	)
}

///////////////////////////////////////////////////////////
// GET /api/user/notifications/count
///////////////////////////////////////////////////////////

// Count is the badge: the unseen, seen, new and total counts with no rows, so
// the app can poll it cheaply from anywhere.
func (c *NotificationController) Count(ctx *gin.Context) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {

		ctx.JSON(http.StatusUnauthorized, helpers.ErrorResponse("unauthorized"))

		return
	}

	counts, err := c.center.CountsFor(userID)

	if err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	ctx.JSON(
		http.StatusOK,
		helpers.SuccessResponse("Notification count fetched successfully", counts),
	)
}

///////////////////////////////////////////////////////////
// POST /api/user/notifications/seen
///////////////////////////////////////////////////////////

// Seen records that the notification screen was opened, which clears the `new`
// flag and the tab dot. It does not mark anything read - seeing the list is not
// reading its entries, and the app still shows unread styling until the person
// opens one.
func (c *NotificationController) Seen(ctx *gin.Context) {

	c.respond(ctx, func(userID uint) (any, error) {
		return c.center.MarkSeen(userID)
	}, "Notifications marked as seen")
}

///////////////////////////////////////////////////////////
// POST /api/user/notifications/read
///////////////////////////////////////////////////////////

// Read marks the posted ids read, or everything when `all` is true.
func (c *NotificationController) Read(ctx *gin.Context) {

	var request NotificationIDsRequest

	if err := ctx.ShouldBindJSON(&request); err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	c.respond(ctx, func(userID uint) (any, error) {
		return c.center.MarkRead(userID, request.IDs, request.All)
	}, "Notifications marked as read")
}

///////////////////////////////////////////////////////////
// POST /api/user/notifications/delete
///////////////////////////////////////////////////////////

// Delete removes one notification or several selected ones.
func (c *NotificationController) Delete(ctx *gin.Context) {

	var request NotificationIDsRequest

	if err := ctx.ShouldBindJSON(&request); err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	c.respond(ctx, func(userID uint) (any, error) {
		return c.center.Delete(userID, request.IDs)
	}, "Notifications removed successfully")
}

///////////////////////////////////////////////////////////
// DELETE /api/user/notifications/:id
///////////////////////////////////////////////////////////

// DeleteOne removes a single notification by path id, for clients that would
// rather not post a body to delete one row.
func (c *NotificationController) DeleteOne(ctx *gin.Context) {

	id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)

	if err != nil || id == 0 {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse("invalid notification id"))

		return
	}

	c.respond(ctx, func(userID uint) (any, error) {
		return c.center.Delete(userID, []uint{uint(id)})
	}, "Notification removed successfully")
}

///////////////////////////////////////////////////////////
// POST /api/user/notifications/clear
///////////////////////////////////////////////////////////

// Clear removes every notification on the account.
func (c *NotificationController) Clear(ctx *gin.Context) {

	c.respond(ctx, func(userID uint) (any, error) {
		return c.center.ClearAll(userID)
	}, "All notifications cleared successfully")
}

///////////////////////////////////////////////////////////
// Shared plumbing
///////////////////////////////////////////////////////////

// respond runs one centre action for the authenticated account. The five
// mutating endpoints differ only in which method they call and what they say,
// so the auth check and the error handling live here once.
func (c *NotificationController) respond(
	ctx *gin.Context,
	action func(userID uint) (any, error),
	message string,
) {

	userID := middleware.GetUserID(ctx)

	if userID == 0 {

		ctx.JSON(http.StatusUnauthorized, helpers.ErrorResponse("unauthorized"))

		return
	}

	response, err := action(userID)

	if err != nil {

		ctx.JSON(http.StatusBadRequest, helpers.ErrorResponse(err.Error()))

		return
	}

	ctx.JSON(http.StatusOK, helpers.SuccessResponse(message, response))
}

// notificationQuery reads the list filters off the query string, which is where
// every other paginated list in this API reads them from - including the ones
// mounted on POST.
func notificationQuery(ctx *gin.Context) notifications.ListQuery {

	query := notifications.ListQuery{
		Page:   1,
		Limit:  20,
		Status: ctx.DefaultQuery("status", notifications.StatusAll),
		Search: ctx.DefaultQuery("search", ""),
	}

	if value, err := strconv.Atoi(ctx.Query("page")); err == nil && value > 0 {
		query.Page = value
	}

	if value, err := strconv.Atoi(ctx.Query("limit")); err == nil && value > 0 {
		query.Limit = value
	}

	return query
}
