package notification

import (
	"context"
	"time"

	"gorm.io/gorm"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/model/dto"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
)

// ReadService 执行后台账号与个人客户的幂等已读事务脚本。
type ReadService struct {
	db  *gorm.DB
	now func() time.Time
}

// NewReadService 创建单条已读用例。
func NewReadService(db *gorm.DB) *ReadService {
	return &ReadService{db: db, now: time.Now}
}

// MarkRead 仅首次更新当前接收人的未过期未读通知。
func (s *ReadService) MarkRead(ctx context.Context, recipientID, notificationID uint) error {
	if recipientID == 0 || notificationID == 0 {
		return errors.New(errors.CodeInvalidParam)
	}
	now := s.now().UTC()
	result := s.db.WithContext(ctx).Model(&model.Notification{}).
		Where("id = ? AND recipient_kind = ? AND recipient_id = ? AND is_read = ? AND (expires_at IS NULL OR expires_at > ?)",
			notificationID, constants.NotificationRecipientKindAccount, recipientID, false, now).
		Updates(map[string]any{"is_read": true, "read_at": now})
	if result.Error != nil {
		return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新通知已读状态失败")
	}
	return nil
}

// MarkAllRead 将当前后台账号全部或指定类别的未过期通知幂等标记为已读。
func (s *ReadService) MarkAllRead(ctx context.Context, recipientID uint, request dto.NotificationReadAllRequest) (*dto.NotificationReadAllResponse, error) {
	if recipientID == 0 || !isReadAllCategory(request.Category) {
		return nil, errors.New(errors.CodeInvalidParam)
	}
	now := s.now().UTC()
	db := s.db.WithContext(ctx).Model(&model.Notification{}).
		Where("recipient_kind = ? AND recipient_id = ? AND is_read = ? AND (expires_at IS NULL OR expires_at > ?)",
			constants.NotificationRecipientKindAccount, recipientID, false, now)
	if request.Category != "" {
		db = db.Where("category = ?", request.Category)
	}
	result := db.Updates(map[string]any{"is_read": true, "read_at": now})
	if result.Error != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, result.Error, "批量更新通知已读状态失败")
	}
	return &dto.NotificationReadAllResponse{UpdatedCount: result.RowsAffected}, nil
}

func isReadAllCategory(category string) bool {
	switch category {
	case "", constants.NotificationCategoryApproval, constants.NotificationCategoryExpiry,
		constants.NotificationCategorySync, constants.NotificationCategorySystem:
		return true
	default:
		return false
	}
}

// MarkPersonalRead 仅首次更新当前个人客户可见的未过期未读通知。
func (s *ReadService) MarkPersonalRead(ctx context.Context, customerID, notificationID uint) error {
	if customerID == 0 || notificationID == 0 {
		return errors.New(errors.CodeInvalidParam)
	}
	now := s.now().UTC()
	result := personalReadScope(s.db.WithContext(ctx).Model(&model.Notification{}), customerID, now).
		Where("id = ? AND is_read = ?", notificationID, false).
		Updates(map[string]any{"is_read": true, "read_at": now})
	if result.Error != nil {
		return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新个人客户通知已读状态失败")
	}
	return nil
}

// MarkAllPersonalRead 将当前个人客户可见的全部未过期通知幂等标记为已读。
func (s *ReadService) MarkAllPersonalRead(ctx context.Context, customerID uint) (*dto.NotificationReadAllResponse, error) {
	if customerID == 0 {
		return nil, errors.New(errors.CodeInvalidParam)
	}
	now := s.now().UTC()
	result := personalReadScope(s.db.WithContext(ctx).Model(&model.Notification{}), customerID, now).
		Where("is_read = ?", false).
		Updates(map[string]any{"is_read": true, "read_at": now})
	if result.Error != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, result.Error, "批量更新个人客户通知已读状态失败")
	}
	return &dto.NotificationReadAllResponse{UpdatedCount: result.RowsAffected}, nil
}

func personalReadScope(db *gorm.DB, customerID uint, now time.Time) *gorm.DB {
	return db.Where(`recipient_kind = ? AND recipient_id = ?
		AND category IN ? AND type IN ? AND (expires_at IS NULL OR expires_at > ?)`,
		constants.NotificationRecipientKindPersonalCustomer,
		customerID,
		[]string{constants.NotificationCategoryApproval, constants.NotificationCategoryExpiry, constants.NotificationCategorySystem},
		[]string{constants.NotificationTypePackageExpiring, constants.NotificationTypeExchangeShippingCreated},
		now,
	)
}
