// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later

package actions

import (
	"context"
	"fmt"
	"time"

	actions_model "forgejo.org/models/actions"
	"forgejo.org/models/db"
	"forgejo.org/modules/actions"
	"forgejo.org/modules/log"
	"forgejo.org/modules/optional"
	"forgejo.org/modules/timeutil"
)

var (
	transferLingeringLogsMax   = 20000
	transferLingeringLogsSleep = 50 * time.Millisecond
	transferLingeringLogsOld   = 24 * time.Hour
)

func TransferLingeringLogs(ctx context.Context) error {
	return transferLingeringLogs(ctx, transferLingeringLogsOpts(time.Now()))
}

func transferLingeringLogsOpts(now time.Time) actions_model.FindTaskOptions {
	// performance considerations: the search is linear because
	// LogInStorage has no index. But it is bounded by
	// LogExpired which is always true for older records and has an index.
	return actions_model.FindTaskOptions{
		Status:       actions_model.DoneStatuses(),
		LogInStorage: optional.Some(false),
		LogExpired:   optional.Some(false),
		// do it after a long delay to avoid any possibility of race with an ongoing operation
		// as it is not protected by a transaction
		UpdatedBefore: timeutil.TimeStamp(now.Add(-transferLingeringLogsOld).Unix()),
	}
}

func transferLingeringLogs(ctx context.Context, opts actions_model.FindTaskOptions) error {
	count := 0
	err := db.Iterate(ctx, opts.ToConds(), func(ctx context.Context, task *actions_model.ActionTask) error {
		if err := TransferLogsAndUpdateLogInStorage(ctx, task); err != nil {
			return err
		}
		log.Debug("processed task %d", task.ID)
		count++
		if count < transferLingeringLogsMax {
			log.Debug("sleeping %v to not stress the storage", transferLingeringLogsSleep)
			time.Sleep(transferLingeringLogsSleep)
		}
		if count >= transferLingeringLogsMax {
			return fmt.Errorf("stopped after processing %v tasks and will resume later", transferLingeringLogsMax)
		}
		return nil
	})
	if count >= transferLingeringLogsMax {
		log.Info("%v", err)
		return nil
	}
	if count > 0 {
		log.Info("processed %d tasks", count)
	}
	return err
}

func TransferLogsAndUpdateLogInStorage(ctx context.Context, task *actions_model.ActionTask) error {
	if task.LogInStorage {
		return nil
	}
	remove, err := TransferLogs(ctx, task.LogFilename)
	if err != nil {
		return err
	}
	task.LogInStorage = true
	if err := actions_model.UpdateTask(ctx, task, "log_in_storage"); err != nil {
		return err
	}
	remove()

	return nil
}

func TransferLogs(ctx context.Context, logFilename string) (func(), error) {
	exists, err := actions.ExistsLogs(ctx, logFilename)
	if err != nil {
		return nil, err
	}
	if !exists {
		return func() {}, nil
	}
	return actions.TransferLogs(ctx, logFilename)
}
