package api_key

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/dchest/uniuri"
	"github.com/gin-gonic/gin"
	"github.com/onsi/gomega"
	pingcaplog "github.com/pingcap/log"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"go.uber.org/zap/zaptest/observer"

	"github.com/pingcap-inc/clinic/pkg/auth/utils"
)

func Test_isUploadUri(t *testing.T) {
	g := gomega.NewWithT(t)

	g.Expect(isAllowUri("/ab", []string{}, nil)).To(gomega.BeFalse())

	g.Expect(isAllowUri("/clinic/api/v1/diag/precreate?xxx", []string{"/clinic/api/v1/diag"}, nil)).To(gomega.BeTrue())
}

func Test_isAllowUriRegexp(t *testing.T) {
	g := gomega.NewWithT(t)

	g.Expect(isAllowUri("/clinic/api/v1/orgs/12132432143215/clusters", []string{"/clinic/api/v1/orgs"}, nil)).To(gomega.BeTrue())

	g.Expect(isAllowUri("/ab", []string{"/ba"}, nil)).To(gomega.BeFalse())
}

func Test_DecodeApiKey(t *testing.T) {
	g := gomega.NewWithT(t)

	// Test decoding a valid API key
	jsonKey := ApiKeyJson{
		Key:    "test-key-123",
		UserId: 42,
		OrgId:  100,
	}
	jsonBytes, err := json.Marshal(jsonKey)
	g.Expect(err).To(gomega.BeNil())

	encoded := base64.StdEncoding.EncodeToString(jsonBytes)
	decoded, err := Decode(encoded)
	g.Expect(err).To(gomega.BeNil())
	g.Expect(decoded.Key).To(gomega.Equal("test-key-123"))
	g.Expect(decoded.UserId).To(gomega.Equal(uint(42)))
	g.Expect(decoded.OrgId).To(gomega.Equal(uint(100)))
}

func Test_DecodeInternalApiKey(t *testing.T) {
	g := gomega.NewWithT(t)

	// Test decoding an internal API key (OrgId=0)
	jsonKey := ApiKeyJson{
		Key:    "internal-key-456",
		UserId: 123,
		OrgId:  0, // Internal user
	}
	jsonBytes, err := json.Marshal(jsonKey)
	g.Expect(err).To(gomega.BeNil())

	encoded := base64.StdEncoding.EncodeToString(jsonBytes)
	decoded, err := Decode(encoded)
	g.Expect(err).To(gomega.BeNil())
	g.Expect(decoded.Key).To(gomega.Equal("internal-key-456"))
	g.Expect(decoded.UserId).To(gomega.Equal(uint(123)))
	g.Expect(decoded.OrgId).To(gomega.Equal(uint(0)))
}

func Test_DecodeInvalidApiKey(t *testing.T) {
	g := gomega.NewWithT(t)

	// Test decoding invalid base64
	_, err := Decode("not-valid-base64!!!")
	g.Expect(err).To(gomega.Equal(ErrInvalidApiKey))

	// Test decoding invalid JSON
	encoded := base64.StdEncoding.EncodeToString([]byte("not-json"))
	_, err = Decode(encoded)
	g.Expect(err).To(gomega.Equal(ErrInvalidApiKey))
}

func Test_IsValid(t *testing.T) {
	g := gomega.NewWithT(t)

	userID := uint(42)
	rawKey := uniuri.NewLen(32)

	// Hash the key
	hashedKey, err := utils.EncodePassword(rawKey, fmt.Sprintf("api_key%d", userID))
	g.Expect(err).To(gomega.BeNil())

	// Test valid key
	keyJson := &ApiKeyJson{
		Key:    rawKey,
		UserId: userID,
		OrgId:  0,
	}
	isValid, err := IsValid(keyJson, hashedKey)
	g.Expect(err).To(gomega.BeNil())
	g.Expect(isValid).To(gomega.BeTrue())

	// Test invalid key
	keyJson.Key = "wrong-key"
	isValid, err = IsValid(keyJson, hashedKey)
	g.Expect(err).To(gomega.BeNil())
	g.Expect(isValid).To(gomega.BeFalse())
}

func Test_GetAllowUris(t *testing.T) {
	g := gomega.NewWithT(t)

	// Internal user should get more URIs
	internalUris, internalRegexs := getAllowUris(true)
	g.Expect(len(internalUris)).To(gomega.BeNumerically(">", 0))
	g.Expect(len(internalRegexs)).To(gomega.BeNumerically(">", 0))

	// External user should get upload URIs only
	externalUris, externalRegexs := getAllowUris(false)
	g.Expect(len(externalUris)).To(gomega.BeNumerically(">", 0))
	g.Expect(len(externalRegexs)).To(gomega.Equal(0))

	// Internal should have more URIs than external
	g.Expect(len(internalUris)).To(gomega.BeNumerically(">", len(externalUris)))
}

func TestMWTryAuthDoesNotLogRawAPIKey(t *testing.T) {
	core, observed := observer.New(zapcore.ErrorLevel)
	restoreLogger := pingcaplog.ReplaceGlobals(
		zap.New(core),
		&pingcaplog.ZapProperties{Core: core, Level: zap.NewAtomicLevelAt(zapcore.ErrorLevel)},
	)
	t.Cleanup(restoreLogger)

	const rawKey = "raw-api-key-must-not-be-logged"
	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/clinic/api/v1/diagnosis/test", nil)
	ctx.Request.Header.Set("Authorization", "Bearer "+rawKey)

	MWTryAuth(ctx, nil, true)

	entries := observed.All()
	gomega.NewWithT(t).Expect(entries).ToNot(gomega.BeEmpty())
	for _, entry := range entries {
		fields, err := json.Marshal(entry.ContextMap())
		if err != nil {
			t.Fatal(err)
		}
		gomega.NewWithT(t).Expect(strings.Join([]string{entry.Message, string(fields)}, " ")).ToNot(gomega.ContainSubstring(rawKey))
	}
}
