package api_test

import (
	"crypto/rand"
	"crypto/rsa"
	"testing"
	"time"

	"github.com/golang-jwt/jwt/v5"
	. "github.com/smartystreets/goconvey/convey"

	zerr "zotregistry.dev/zot/errors"
	"zotregistry.dev/zot/pkg/api"
)

func TestBearerAuthorizer(t *testing.T) {
	Convey("Test bearer token authorization", t, func() {
		signingMethod := jwt.SigningMethodRS256

		privKey, err := rsa.GenerateKey(rand.Reader, 2048)
		if err != nil {
			panic(err)
		}

		pubKey := privKey.Public()

		authorizer := api.NewBearerAuthorizer("realm", "service", pubKey)

		Convey("Empty authorization header given", func() {
			err := authorizer.Authorize("", nil)
			So(err, ShouldBeError, zerr.ErrNoBearerToken)
		})

		Convey("Valid token", func() {
			access := []api.ResourceAccess{
				{
					Name:    "authorized-repository",
					Type:    "repository",
					Actions: []string{"pull"},
				},
			}

			now := time.Now()
			claims := api.ClaimsWithAccess{
				Access: access,
				RegisteredClaims: jwt.RegisteredClaims{
					ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 1)),
					IssuedAt:  jwt.NewNumericDate(now),
					Issuer:    "Zot",
					Audience:  []string{"Zot Registry"},
				},
			}

			token, err := jwt.NewWithClaims(signingMethod, claims).SignedString(privKey)
			if err != nil {
				panic(err)
			}

			authHeader := "Bearer " + token

			Convey("Unauthorized type", func() {
				requested := &api.ResourceAction{
					Type:   "registry",
					Name:   "catalog",
					Action: "*",
				}

				err := authorizer.Authorize(authHeader, requested)
				So(err, ShouldHaveSameTypeAs, &api.AuthChallengeError{})
				So(err, ShouldBeError, zerr.ErrInsufficientScope)
			})

			Convey("Unauthorized name", func() {
				requested := &api.ResourceAction{
					Type:   "repository",
					Name:   "unauthorized-repository",
					Action: "pull",
				}

				err := authorizer.Authorize(authHeader, requested)
				So(err, ShouldHaveSameTypeAs, &api.AuthChallengeError{})
				So(err, ShouldBeError, zerr.ErrInsufficientScope)
			})

			Convey("Unauthorized action", func() {
				requested := &api.ResourceAction{
					Type:   "repository",
					Name:   "authorized-repository",
					Action: "push",
				}

				err := authorizer.Authorize(authHeader, requested)
				So(err, ShouldHaveSameTypeAs, &api.AuthChallengeError{})
				So(err, ShouldBeError, zerr.ErrInsufficientScope)
			})

			Convey("Successful authorization with requested access", func() {
				requested := &api.ResourceAction{
					Type:   "repository",
					Name:   "authorized-repository",
					Action: "pull",
				}

				err := authorizer.Authorize(authHeader, requested)
				So(err, ShouldBeNil)
			})

			Convey("Successful authorization without requested access", func() {
				err := authorizer.Authorize(authHeader, nil)
				So(err, ShouldBeNil)
			})
		})

		Convey("Invalid token", func() {
			authHeader := "invalid"

			err := authorizer.Authorize(authHeader, nil)
			So(err, ShouldWrap, zerr.ErrInvalidBearerToken)
		})
	})
}
