/******************************************************************************
 * Copyright (c)  2025 PingCAP, Inc.                                          *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 * http://www.apache.org/licenses/LICENSE-2.0                                 *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

package interceptor

import (
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/observability/pkg/common/constants"
)

func Test_InitInternalAuthCache(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		InitInternalAuthCache()
		assert.NotNil(t, internalTokenCache)
	})
}

func Test_InternalAuth_Basic(t *testing.T) {
	gin.SetMode(gin.TestMode)

	t.Run("cache not initialized", func(t *testing.T) {
		internalTokenCache = nil
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Header: map[string][]string{
				"Authorization": {"Bearer testToken"},
			},
		}

		InternalAuth(c)
		assert.Equal(t, http.StatusInternalServerError, w.Code)
	})

	t.Run("token empty", func(t *testing.T) {
		InitInternalAuthCache()
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Header: map[string][]string{},
		}

		InternalAuth(c)
		assert.Equal(t, http.StatusUnauthorized, w.Code)
	})

	t.Run("token prefix not match", func(t *testing.T) {
		InitInternalAuthCache()
		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Header: map[string][]string{
				"Authorization": {"Basic testToken"},
			},
		}

		InternalAuth(c)
		assert.Equal(t, http.StatusUnauthorized, w.Code)
	})
}

func Test_InternalAuth_WithCache(t *testing.T) {
	gin.SetMode(gin.TestMode)
	InitInternalAuthCache()

	t.Run("cached token valid", func(t *testing.T) {
		ClearInternalTokenCache()
		internalTokenCache.Set("validToken", cachedTokenInfo{
			GithubUsername: "testuser",
			Exp:            time.Now().Unix() + 3600,
		}, time.Hour)

		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Header: map[string][]string{
				"Authorization": {"Bearer validToken"},
			},
		}

		InternalAuth(c)
		assert.Equal(t, http.StatusOK, w.Code)
		assert.Equal(t, "testuser", c.Request.Header.Get(constants.HeaderUserId))

		ClearInternalTokenCache()
	})

	t.Run("cached token near expiry but still valid", func(t *testing.T) {
		ClearInternalTokenCache()
		internalTokenCache.Set("nearExpiryToken", cachedTokenInfo{
			GithubUsername: "testuser",
			Exp:            time.Now().Unix() + 400,
		}, time.Hour)

		w := httptest.NewRecorder()
		c, _ := gin.CreateTestContext(w)
		c.Request = &http.Request{
			Header: map[string][]string{
				"Authorization": {"Bearer nearExpiryToken"},
			},
		}

		InternalAuth(c)
		assert.Equal(t, http.StatusOK, w.Code)

		ClearInternalTokenCache()
	})
}

func Test_verifyInternalToken_Basic(t *testing.T) {
	t.Run("token is empty", func(t *testing.T) {
		InitInternalAuthCache()
		_, err := verifyInternalToken("")
		assert.Error(t, err)
	})

	t.Run("token prefix not match", func(t *testing.T) {
		InitInternalAuthCache()
		_, err := verifyInternalToken("Basic testToken")
		assert.Error(t, err)
	})
}

func Test_verifyInternalToken_WithCache(t *testing.T) {
	InitInternalAuthCache()

	t.Run("token is cached and valid", func(t *testing.T) {
		ClearInternalTokenCache()
		internalTokenCache.Set("cachedValidToken", cachedTokenInfo{
			GithubUsername: "cacheduser",
			Exp:            time.Now().Unix() + 3600,
		}, time.Hour)

		username, err := verifyInternalToken("Bearer cachedValidToken")
		assert.NoError(t, err)
		assert.Equal(t, "cacheduser", username)

		ClearInternalTokenCache()
	})
}

func Test_GetAndClearInternalTokenCache(t *testing.T) {
	t.Run("get cache", func(t *testing.T) {
		InitInternalAuthCache()
		c := GetInternalTokenCache()
		assert.NotNil(t, c)
	})

	t.Run("clear cache", func(t *testing.T) {
		InitInternalAuthCache()
		internalTokenCache.Set("test", cachedTokenInfo{GithubUsername: "user", Exp: 9999999999}, time.Hour)
		assert.Equal(t, 1, internalTokenCache.ItemCount())

		ClearInternalTokenCache()
		assert.Equal(t, 0, internalTokenCache.ItemCount())
	})
}

func Test_cachedTokenInfo(t *testing.T) {
	info := cachedTokenInfo{
		GithubUsername: "testuser",
		Exp:            1234567890,
	}
	assert.Equal(t, "testuser", info.GithubUsername)
	assert.Equal(t, int64(1234567890), info.Exp)
}
