package server

import (
	"fmt"
	"net/http"
	"net/url"
	"testing"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/httpz"
	"go.uber.org/mock/gomock"
)

const (
	temporalUserEmailClaimName = "https://saas-api.tmprl.cloud/user/email"
)

var (
	insecureTestSecretKey = []byte("any-insecure-secret-key")
)

func TestOIDC_Authorize(t *testing.T) {
	// GIVEN
	tests := []struct {
		name             string
		requestPath      string
		requestNamespace string
		requestUserEmail string
		expectedError    string
	}{
		{
			name:             "wrong path in request",
			requestPath:      "/",
			requestNamespace: "authorized-namespace",
			requestUserEmail: "authorized-user@example.com",
			expectedError:    "invalid decode URI path",
		},
		{
			name:             "no namespace in request",
			requestPath:      decodePath,
			requestNamespace: "",
			requestUserEmail: "authorized-user@example.com",
			expectedError:    "invalid namespace",
		},
		{
			name:             "wrong namespace in request",
			requestPath:      decodePath,
			requestNamespace: "unauthorized-namespace",
			requestUserEmail: "authorized-user@example.com",
			expectedError:    "invalid namespace",
		},
		{
			name:             "no user email in request access token",
			requestPath:      decodePath,
			requestNamespace: "authorized-namespace",
			requestUserEmail: "",
			expectedError:    "user \"\" is not authorized",
		},
		{
			name:             "unauthorized user email in request access token",
			requestPath:      decodePath,
			requestNamespace: "authorized-namespace",
			requestUserEmail: "unauthorized-user@example.com",
			expectedError:    "user \"unauthorized-user@example.com\" is not authorized",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			keyfuncMock := NewMockKeyfunc(ctrl)
			keyfuncMock.EXPECT().Keyfunc(gomock.Any()).DoAndReturn(testKeyFunc).MaxTimes(1)

			requestToken := generateTestToken(t, tc.requestUserEmail)

			provider := &oidcProvider{
				namespace:            "authorized-namespace",
				authorizedUserEmails: []string{"authorized-user@example.com"},
				jwks:                 keyfuncMock,
			}
			r := &http.Request{
				URL: &url.URL{
					Path: tc.requestPath,
				},
				Header: http.Header{
					temporalNamespaceHeaderName: []string{tc.requestNamespace},
					httpz.AuthorizationHeader:   []string{fmt.Sprintf("Bearer %s", requestToken)},
				},
			}

			// WHEN
			err := provider.authorize(r)

			// THEN
			if tc.expectedError == "" {
				assert.NoError(t, err)
			} else {
				assert.EqualError(t, err, tc.expectedError)
			}
		})
	}
}

func generateTestToken(t *testing.T, email string) string {
	now := time.Now()

	// Create the claims
	claims := jwt.MapClaims{
		"iss":                      "example.com",
		"sub":                      "1234567890",
		"aud":                      "any-audience",
		"exp":                      now.Add(time.Hour).Unix(),
		"nbf":                      now.Unix(),
		"iat":                      now.Unix(),
		temporalUserEmailClaimName: email,
	}

	token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(insecureTestSecretKey)
	require.NoError(t, err)
	return token
}

func testKeyFunc(t *jwt.Token) (any, error) {
	return insecureTestSecretKey, nil
}
