package rest

import (
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/queryspec"
)

const productNotificationDefaultLimit = 50
const productNotificationMaxLimit = 200

type ProductNotificationsHandler struct {
	records contract.Records
	query   contract.Query
}

type productNotificationResponse struct {
	ID           string `json:"id"`
	RecipientID  string `json:"recipientId"`
	Type         string `json:"type"`
	Unread       bool   `json:"unread"`
	CreatedDate  string `json:"createdDate"`
	MessageBody  string `json:"messageBody"`
	MessageTitle string `json:"messageTitle"`
	URL          string `json:"url,omitempty"`
}

func NewProductNotificationsHandler(records contract.Records, query contract.Query) *ProductNotificationsHandler {
	return &ProductNotificationsHandler{records: records, query: query}
}

func (h *ProductNotificationsHandler) RegisterRoutes(group *gin.RouterGroup) {
	group.GET("/notifications", h.ListNotifications)
	group.POST("/notification-reads", h.MarkAllNotificationsRead)
	group.POST("/notification-reads/:id", h.MarkNotificationRead)
}

func (h *ProductNotificationsHandler) ListNotifications(c *gin.Context) {
	user := MustGetUser(c)
	if h.query == nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "notification query unavailable"})
		return
	}

	limit, ok := parsePositiveLimit(c, "limit", productNotificationDefaultLimit, productNotificationMaxLimit)
	if !ok {
		return
	}

	result, err := h.query.Query(c.Request.Context(), queryspec.QuerySpec{
		ObjectAPIName: constants.TableNotification,
		SelectFields: []string{
			constants.FieldNotification_ID,
			constants.FieldNotification_RecipientID,
			constants.FieldNotification_Title,
			constants.FieldNotification_Body,
			constants.FieldNotification_Link,
			constants.FieldNotification_IsRead,
			constants.FieldNotification_NotificationType,
			constants.FieldNotification_CreatedDate,
		},
		Filter: queryspec.CriteriaPredicate([]queryspec.QueryCriterion{
			{Field: constants.FieldNotification_RecipientID, Op: "=", Val: user.ID},
		}),
		SortField:     constants.FieldNotification_CreatedDate,
		SortDirection: "DESC",
		Limit:         limit,
	}, user)
	if err != nil {
		RespondAppError(c, err)
		return
	}

	records := []contract.Record{}
	if result != nil {
		records = result.Records
	}
	notifications := make([]productNotificationResponse, 0, len(records))
	unreadCount := 0
	for _, record := range records {
		response := productNotificationFromRecord(record)
		if response.Unread {
			unreadCount++
		}
		notifications = append(notifications, response)
	}

	c.JSON(http.StatusOK, gin.H{
		"notifications": notifications,
		"unreadCount":   unreadCount,
	})
}

func (h *ProductNotificationsHandler) MarkNotificationRead(c *gin.Context) {
	user := MustGetUser(c)
	if h.records == nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "notification records unavailable"})
		return
	}

	id, ok := RequireParam(c, "id")
	if !ok {
		return
	}
	if !validateID(id) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid notification id"})
		return
	}
	if !h.notificationBelongsToUser(c, id, user) {
		return
	}

	if err := h.records.Update(c.Request.Context(), constants.TableNotification, id, contract.Record{
		constants.FieldNotification_IsRead: true,
	}, user); err != nil {
		RespondAppError(c, err)
		return
	}

	c.Status(http.StatusNoContent)
}

func (h *ProductNotificationsHandler) MarkAllNotificationsRead(c *gin.Context) {
	user := MustGetUser(c)
	if h.records == nil || h.query == nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "notification records unavailable"})
		return
	}

	result, err := h.query.Query(c.Request.Context(), queryspec.QuerySpec{
		ObjectAPIName: constants.TableNotification,
		SelectFields: []string{
			constants.FieldNotification_ID,
			constants.FieldNotification_RecipientID,
			constants.FieldNotification_IsRead,
		},
		Filter: queryspec.CriteriaPredicate([]queryspec.QueryCriterion{
			{Field: constants.FieldNotification_RecipientID, Op: "=", Val: user.ID},
			{Field: constants.FieldNotification_IsRead, Op: "=", Val: false},
		}),
		SortField:     constants.FieldNotification_CreatedDate,
		SortDirection: "DESC",
		Limit:         productNotificationMaxLimit,
	}, user)
	if err != nil {
		RespondAppError(c, err)
		return
	}

	if result != nil {
		for _, record := range result.Records {
			id, _ := record[constants.FieldNotification_ID].(string)
			if !validateID(id) {
				continue
			}
			if err := h.records.Update(c.Request.Context(), constants.TableNotification, id, contract.Record{
				constants.FieldNotification_IsRead: true,
			}, user); err != nil {
				RespondAppError(c, err)
				return
			}
		}
	}

	c.Status(http.StatusNoContent)
}

func (h *ProductNotificationsHandler) notificationBelongsToUser(c *gin.Context, id string, user *models.UserSession) bool {
	record, err := h.records.Retrieve(c.Request.Context(), constants.TableNotification, id, user)
	if err != nil {
		RespondAppError(c, err)
		return false
	}
	if stringValue(record[constants.FieldNotification_RecipientID]) != user.ID {
		c.JSON(http.StatusNotFound, gin.H{"error": "notification not found"})
		return false
	}
	return true
}

func productNotificationFromRecord(record contract.Record) productNotificationResponse {
	isRead := boolValue(record[constants.FieldNotification_IsRead])
	return productNotificationResponse{
		ID:           stringValue(record[constants.FieldNotification_ID]),
		RecipientID:  stringValue(record[constants.FieldNotification_RecipientID]),
		Type:         stringValue(record[constants.FieldNotification_NotificationType]),
		Unread:       !isRead,
		CreatedDate:  timeStringValue(record[constants.FieldNotification_CreatedDate]),
		MessageBody:  stringValue(record[constants.FieldNotification_Body]),
		MessageTitle: stringValue(record[constants.FieldNotification_Title]),
		URL:          stringValue(record[constants.FieldNotification_Link]),
	}
}

func stringValue(value any) string {
	if s, ok := value.(string); ok {
		return s
	}
	return ""
}

func boolValue(value any) bool {
	if b, ok := value.(bool); ok {
		return b
	}
	return false
}

func timeStringValue(value any) string {
	switch v := value.(type) {
	case time.Time:
		return v.Format(time.RFC3339Nano)
	case string:
		return v
	default:
		return ""
	}
}
