package Astrologer

import (
	"net/http"
	"strconv"

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

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

///////////////////////////////////////////////////////////
// Notification Controller
///////////////////////////////////////////////////////////
//
// The astrologer app's notification centre. The two stacks keep their own
// controllers, so this mirrors controllers/user/notification_controller.go
// rather than importing it, but both sit on the same notifications.Center and
// answer with the same request and response shapes - the web app serves both
// audiences from one screen and should not need two renderers.
//
// On this side `user_id` in the context is the astrologer's users.id, which is
// exactly what user_notifications.userId is keyed on, so no astrologers.id
// lookup is needed here.

type NotificationController struct {
	center *notifications.Center
}

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

	return &NotificationController{center: center}
}

// NotificationIDsRequest is what mark-read and delete take: one id or many in
// `ids`, plus `all` on mark-read for clearing the badge in one call.
type NotificationIDsRequest struct {
	IDs []uint `json:"ids"`
	All bool   `json:"all"`
}

///////////////////////////////////////////////////////////
// GET /api/astrologer/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/astrologer/notifications/count
///////////////////////////////////////////////////////////

// Count is the badge: unseen, seen, new and total, with no rows.
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/astrologer/notifications/seen
///////////////////////////////////////////////////////////

// Seen records that the screen was opened, clearing the `new` flag without
// marking anything read.
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/astrologer/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/astrologer/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/astrologer/notifications/:id
///////////////////////////////////////////////////////////

// DeleteOne removes a single notification by path id.
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/astrologer/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
///////////////////////////////////////////////////////////

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))
}

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
}
