// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package lfs

import (
	"strings"
	"testing"

	"gitea.dev/models/db"
	perm_model "gitea.dev/models/perm"
	repo_model "gitea.dev/models/repo"
	"gitea.dev/models/unittest"
	user_model "gitea.dev/models/user"
	"gitea.dev/services/contexttest"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestMain(m *testing.M) {
	unittest.MainTest(m)
}

func TestAuthenticate(t *testing.T) {
	require.NoError(t, unittest.PrepareTestDatabase())
	ctx, _ := contexttest.MockContext(t, "/")

	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})

	getUserToken := func(op string, userID int64, repo *repo_model.Repository) string {
		s, _ := GetLFSAuthTokenWithBearer(AuthTokenOptions{Op: op, UserID: userID, RepoID: repo.ID})
		_, token, _ := strings.Cut(s, " ")
		return token
	}

	t.Run("handleLFSToken", func(t *testing.T) {
		u, err := handleLFSToken(ctx, "", repo1, perm_model.AccessModeRead)
		require.Error(t, err)
		assert.Nil(t, u)

		u, err = handleLFSToken(ctx, "invalid", repo1, perm_model.AccessModeRead)
		require.Error(t, err)
		assert.Nil(t, u)

		u, err = handleLFSToken(ctx, getUserToken("download", 2, repo1), repo1, perm_model.AccessModeRead)
		require.NoError(t, err)
		assert.EqualValues(t, 2, u.ID)
	})

	t.Run("authenticate", func(t *testing.T) {
		const prefixBearer = "Bearer "
		token := getUserToken("download", 2, repo1)
		assert.False(t, authenticate(ctx, repo1, "", true, false))
		assert.False(t, authenticate(ctx, repo1, prefixBearer+"invalid", true, false))
		assert.True(t, authenticate(ctx, repo1, prefixBearer+token, true, false))
	})

	handleLFSTokenTestPerm := func(op string, userID int64, repo *repo_model.Repository, accessMode perm_model.AccessMode) error {
		token := getUserToken(op, userID, repo)
		u, err := handleLFSToken(ctx, token, repo, accessMode)
		if err == nil {
			assert.Equal(t, userID, u.ID)
		}
		return err
	}

	t.Run("handleLFSToken blocks prohibited users", func(t *testing.T) {
		user37 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 37})

		// prohibited user
		assert.True(t, user37.ProhibitLogin)
		err := handleLFSTokenTestPerm("download", 37, repo1, perm_model.AccessModeRead)
		assert.ErrorContains(t, err, "not allowed to access any repository")

		// normal user
		_, _ = db.GetEngine(t.Context()).ID(37).Cols("prohibit_login").Update(&user_model.User{ProhibitLogin: false})
		err = handleLFSTokenTestPerm("download", 37, repo1, perm_model.AccessModeRead)
		assert.NoError(t, err)

		// inactive user
		_, _ = db.GetEngine(t.Context()).ID(37).Cols("is_active").Update(&user_model.User{IsActive: false})
		err = handleLFSTokenTestPerm("download", 37, repo1, perm_model.AccessModeRead)
		assert.ErrorContains(t, err, "not allowed to access any repository")
	})

	t.Run("handleLFSToken blocks users without repo access", func(t *testing.T) {
		repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
		err := handleLFSTokenTestPerm("download", 10, repo2, perm_model.AccessModeRead)
		assert.ErrorContains(t, err, "no permission to access the repository")
	})

	t.Run("handleLFSToken requires write access for uploads", func(t *testing.T) {
		err := handleLFSTokenTestPerm("download", 10, repo1, perm_model.AccessModeRead)
		assert.NoError(t, err)
		err = handleLFSTokenTestPerm("upload", 10, repo1, perm_model.AccessModeWrite)
		assert.ErrorContains(t, err, "no permission to access the repository")
	})

	t.Run("handleLFSToken allows writes for authorized users", func(t *testing.T) {
		err := handleLFSTokenTestPerm("upload", 2, repo1, perm_model.AccessModeWrite)
		assert.NoError(t, err)
	})
}
