package notification

import (
	"context"
	"time"

	"go.uber.org/zap"
	"gorm.io/gorm"

	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
)

// CleanupService 按通知类别的保留期限分批删除过期通知事实。
type CleanupService struct {
	db     *gorm.DB
	logger *zap.Logger
	now    func() time.Time
}

// NewCleanupService 创建通知保留清理服务。
func NewCleanupService(db *gorm.DB, logger *zap.Logger) *CleanupService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &CleanupService{db: db, logger: logger, now: time.Now}
}

// Run 按类别、创建时间和稳定主键执行有界分批清理。
func (s *CleanupService) Run(ctx context.Context) error {
	policies := []struct {
		category string
		days     int
	}{
		{constants.NotificationCategoryApproval, constants.NotificationApprovalRetentionDays},
		{constants.NotificationCategoryExpiry, constants.NotificationExpiryRetentionDays},
		{constants.NotificationCategorySync, constants.NotificationSyncRetentionDays},
		{constants.NotificationCategorySystem, constants.NotificationSystemRetentionDays},
	}
	for _, policy := range policies {
		deleted, err := s.cleanupCategory(ctx, policy.category, s.now().UTC().AddDate(0, 0, -policy.days))
		if err != nil {
			return err
		}
		s.logger.Info("站内通知保留清理完成",
			zap.String("category", policy.category), zap.Int64("deleted_count", deleted))
	}
	return nil
}

func (s *CleanupService) cleanupCategory(ctx context.Context, category string, cutoff time.Time) (int64, error) {
	var total int64
	for batch := 0; batch < constants.NotificationCleanupMaxBatches; batch++ {
		result := s.db.WithContext(ctx).Exec(`WITH candidates AS (
            SELECT id FROM tb_notification
            WHERE category = ? AND created_at < ?
            ORDER BY created_at ASC, id ASC
            LIMIT ?
        )
        DELETE FROM tb_notification AS notification
        USING candidates
        WHERE notification.id = candidates.id`, category, cutoff, constants.NotificationCleanupBatchSize)
		if result.Error != nil {
			return total, errors.Wrap(errors.CodeDatabaseError, result.Error, "清理站内通知失败")
		}
		total += result.RowsAffected
		if result.RowsAffected < constants.NotificationCleanupBatchSize {
			break
		}
	}
	return total, nil
}
