package thirdparty

import (
	"context"
	"net/http"
	"strings"
	"time"

	"forge.cadoles.com/Cadoles/emissary/internal/auth"
	"forge.cadoles.com/Cadoles/emissary/internal/jwk"
	"github.com/lestrrat-go/jwx/v2/jwt"
	"github.com/pkg/errors"
)

const DefaultAcceptableSkew = 5 * time.Minute

type (
	GetKeySet    func(context.Context) (jwk.Set, error)
	GetTokenRole func(context.Context, jwt.Token) (string, error)
)

type Authenticator struct {
	getKeySet      GetKeySet
	getTokenRole   GetTokenRole
	acceptableSkew time.Duration
}

// Authenticate implements auth.Authenticator.
func (a *Authenticator) Authenticate(ctx context.Context, r *http.Request) (auth.User, error) {
	authorization := r.Header.Get("Authorization")
	if authorization == "" {
		return nil, errors.WithStack(auth.ErrUnauthenticated)
	}

	rawToken := strings.TrimPrefix(authorization, "Bearer ")
	if rawToken == "" {
		return nil, errors.WithStack(auth.ErrUnauthenticated)
	}

	keys, err := a.getKeySet(ctx)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	token, err := parseToken(ctx, keys, rawToken, a.acceptableSkew)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	rawRole, err := a.getTokenRole(ctx, token)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	if !isValidRole(rawRole) {
		return nil, errors.Errorf("invalid role '%s'", rawRole)
	}

	user := &User{
		subject: token.Subject(),
		role:    Role(rawRole),
	}

	return user, nil
}

func NewAuthenticator(getKeySet GetKeySet, getTokenRole GetTokenRole, acceptableSkew time.Duration) *Authenticator {
	return &Authenticator{
		getTokenRole:   getTokenRole,
		getKeySet:      getKeySet,
		acceptableSkew: acceptableSkew,
	}
}

var _ auth.Authenticator = &Authenticator{}