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

package actions

import (
	"testing"
	"time"

	actions_model "forgejo.org/models/actions"
	dbfs_model "forgejo.org/models/dbfs"
	"forgejo.org/models/unittest"
	"forgejo.org/modules/test"
	"forgejo.org/modules/timeutil"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"xorm.io/builder"
)

func TestServicesActions_transferLingeringLogs(t *testing.T) {
	// it would be easier to dynamically create fixtures instead of injecting them
	// in the database for testing, but the dbfs API does not have what is needed to
	// create them
	defer unittest.OverrideFixtures("services/actions/TestServicesActions_TransferLingeringLogs")()
	require.NoError(t, unittest.PrepareTestDatabase())
	defer test.MockVariableValue(&transferLingeringLogsMax, 2)()
	defer test.MockVariableValue(&transferLingeringLogsOld, 2*24*time.Hour)()
	defer test.MockVariableValue(&transferLingeringLogsSleep, time.Millisecond)()

	now, err := time.Parse("2006-01-02", "2024-12-01")
	require.NoError(t, err)
	old := timeutil.TimeStamp(now.Add(-transferLingeringLogsOld).Unix())

	// a task has a lingering log but was updated more recently than
	// transferLingeringLogsOld
	recentID := int64(2000)
	recent := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: recentID}, builder.Eq{"log_in_storage": false})
	require.Greater(t, recent.Updated, old)

	// a task has logs already in storage but would be garbage collected if it was not
	inStorageID := int64(3000)
	inStorage := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: inStorageID}, builder.Eq{"log_in_storage": true})
	require.Greater(t, old, inStorage.Updated)

	taskWithLingeringLogIDs := []int64{1000, 4000, 5000}
	for _, taskWithLingeringLogID := range taskWithLingeringLogIDs {
		lingeringLog := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: taskWithLingeringLogID}, builder.Eq{"log_in_storage": false})
		require.Greater(t, old, lingeringLog.Updated)
	}
	lingeringLogIDs := []int64{1, 4, 5}

	assert.True(t, unittest.BeanExists(t, &dbfs_model.DbfsMeta{}, builder.In("id", []any{lingeringLogIDs}...)))

	// first pass transfer logs for transferLingeringLogsMax tasks
	require.NoError(t, transferLingeringLogs(t.Context(), transferLingeringLogsOpts(now)))
	assert.True(t, unittest.BeanExists(t, &dbfs_model.DbfsMeta{}, builder.In("id", []any{lingeringLogIDs[transferLingeringLogsMax:]}...)))
	for _, lingeringLogID := range lingeringLogIDs[:transferLingeringLogsMax] {
		unittest.AssertNotExistsBean(t, &dbfs_model.DbfsMeta{ID: lingeringLogID})
	}

	// second pass transfer logs for the remainder tasks and there are none left
	require.NoError(t, transferLingeringLogs(t.Context(), transferLingeringLogsOpts(now)))
	for _, lingeringLogID := range lingeringLogIDs {
		unittest.AssertNotExistsBean(t, &dbfs_model.DbfsMeta{ID: lingeringLogID})
	}

	// third pass is happilly doing nothing
	require.NoError(t, transferLingeringLogs(t.Context(), transferLingeringLogsOpts(now)))

	// verify the tasks that are not to be garbage collected are still present
	assert.True(t, unittest.BeanExists(t, &actions_model.ActionTask{ID: recentID}, builder.Eq{"log_in_storage": false}))
	assert.True(t, unittest.BeanExists(t, &actions_model.ActionTask{ID: inStorageID}, builder.Eq{"log_in_storage": true}))
}
