package tokens

import (
	"errors"
	"net/http"
	"strings"

	"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
	"github.com/FAU-CDI/wisski-distillery/internal/models"
	"golang.org/x/exp/slices"
)

const (
	authHeader = "Authorization" // authorization
	authBearer = "Bearer" + " "  // Prefix for bearer
)

// TokenOf returns the token header found in the given request.
// If r is nil, or there is no token, returns nil.
// Error is only set if there is an error accessing the table that stores tokens.
func (tok *Tokens) TokenOf(r *http.Request) (*models.Token, error) {
	if r == nil {
		return nil, nil
	}

	// make sure that the authorization header exists and starts with the bearer
	auth := r.Header.Get(authHeader)
	if !strings.HasPrefix(auth, authBearer) {
		return nil, nil
	}

	// get the token
	id := strings.TrimSpace(strings.TrimPrefix(auth, authBearer))
	if id == "" {
		return nil, nil
	}

	table, err := tok.table(r.Context())
	if err != nil {
		return nil, err
	}

	// take a single object from the tokens
	var tokenObj models.Token
	res := table.Where(&models.Token{Token: id}).Find(&tokenObj)

	if res.Error != nil {
		return nil, errors.Join(ErrNoToken, res.Error)
	}
	if res.RowsAffected == 0 {
		return nil, nil
	}

	// and return the token object
	return &tokenObj, nil
}

var ErrNoToken = errors.New("no token")

// Check checks if there is a token in the given request and if this request has an appropriate token with the appropriate scope.
//
// If the token is found and has the requested token, returns true, nil.
// If there is a token found, but the specific scope is not set, returns false, nil.
// If there is no valid authentication token found, returns false and an error that wraps ErrNoToken.
// In other cases, other errors may be returned.
//
// Note that the scope may require an parameter to be validated.
// This validation should take place in the appropriate ScopeProvider; which should recursively invoke this method.
func (tok *Tokens) Check(r *http.Request, scope component.Scope) (bool, error) {
	// get the token object from the request
	tokenObj, err := tok.TokenOf(r)
	if tokenObj == nil {
		if err == nil {
			return false, ErrNoToken
		}
		return false, errors.Join(ErrNoToken, err)
	}

	// TODO: Do we need this function?

	// get the scopes
	scopes := tokenObj.GetScopes()
	if scopes == nil {
		// all scopes (implicitly)
		return true, nil
	}

	// else check if they are contained
	return slices.Contains(scopes, string(scope)), nil
}
