package service

import (
	"baron-sso-backend/internal/domain"
	"baron-sso-backend/internal/repository"
	"context"
	"log/slog"
	"time"
)

type RPUsageProjectorWorker struct {
	outbox     repository.RPUsageOutboxRepository
	projection domain.RPUsageProjectionRepository
	interval   time.Duration
	batchSize  int
}

func NewRPUsageProjectorWorker(outbox repository.RPUsageOutboxRepository, projection domain.RPUsageProjectionRepository) *RPUsageProjectorWorker {
	return &RPUsageProjectorWorker{
		outbox:     outbox,
		projection: projection,
		interval:   5 * time.Second,
		batchSize:  50,
	}
}

func (w *RPUsageProjectorWorker) Start(ctx context.Context) {
	if w == nil || w.outbox == nil || w.projection == nil {
		return
	}
	ticker := time.NewTicker(w.interval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		default:
			w.processOnce(ctx)
		}

		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
		}
	}
}

func (w *RPUsageProjectorWorker) processOnce(ctx context.Context) {
	events, err := w.outbox.ListReady(ctx, w.batchSize)
	if err != nil {
		slog.Warn("failed to list rp usage outbox", "error", err)
		return
	}
	for _, event := range events {
		if err := w.outbox.MarkProcessing(ctx, event.ID); err != nil {
			slog.Warn("failed to mark rp usage event processing", "event_id", event.ID, "error", err)
			continue
		}
		if err := w.projection.CreateRPUsageEvent(ctx, event); err != nil {
			nextAttempt := time.Now().Add(backoffDuration(event.RetryCount))
			_ = w.outbox.MarkFailed(ctx, event.ID, err.Error(), nextAttempt)
			slog.Warn("failed to project rp usage event", "event_id", event.ID, "error", err)
			continue
		}
		if err := w.outbox.MarkProcessed(ctx, event.ID); err != nil {
			slog.Warn("failed to mark rp usage event processed", "event_id", event.ID, "error", err)
		}
	}
}

func backoffDuration(retryCount int) time.Duration {
	if retryCount < 0 {
		retryCount = 0
	}
	delay := time.Duration(retryCount+1) * time.Minute
	if delay > 30*time.Minute {
		return 30 * time.Minute
	}
	return delay
}
