package httphandlers

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

	"github.com/fiskerinc/cloud-services/pkg/adminroles"
	"github.com/fiskerinc/cloud-services/pkg/cache"
	"github.com/fiskerinc/cloud-services/pkg/common/authproviders"
	c "github.com/fiskerinc/cloud-services/pkg/common/context"
	"github.com/fiskerinc/cloud-services/pkg/db/queries"
	"github.com/fiskerinc/cloud-services/pkg/jwt"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/utils"
)

var ErrorNoAPITokenHeader = errors.New("no api token header")

type AuthAPIToken struct {
	APITokens   queries.APITokensInterface
	APICalls    queries.APICallsInterface
	JWTAuth     bool
	GroupKey    string
	authJWT     *AuthJWTToken
	cache       *cache.APITokenCache
	onceAuthJTW sync.Once
	onceCache   sync.Once
	AuthBase
}

func (a *AuthAPIToken) GetHandler(requiredRoles map[string][]adminroles.RoleID, next http.HandlerFunc) http.HandlerFunc {
	wrapper := func(w http.ResponseWriter, r *http.Request) {
		var err error
		var ctx context.Context

		ctx, err = a.Check(requiredRoles, r)
		if errors.Is(err, ErrorNoAPITokenHeader) && a.JWTAuth {
			ctx, err = a.GetJWTAuth().Check(requiredRoles, r)
		}

		if err != nil {
			logger.Warn().Msgf("AuthAPIToken %s %s '%v'", r.Method, r.RequestURI, err)
			utils.RespError(w, http.StatusUnauthorized, err.Error())
			return
		}

		if ctx != nil {
			r = r.WithContext(ctx)
		}
		next.ServeHTTP(w, r)
	}

	return wrapper
}

func (a *AuthAPIToken) Check(requiredRoles map[string][]adminroles.RoleID, r *http.Request) (context.Context, error) {
	// if there are no required roles, anyone can access
	if !a.hasRoles(requiredRoles) {
		return r.Context(), nil
	}

	token, ok := a.HasAPIToken(r)
	if !ok {
		return nil, ErrorNoAPITokenHeader
	}

	ctx := context.WithValue(r.Context(), ClientIDContextKey, token)
	r = r.WithContext(ctx)

	// API Token is hard coded as provider
	r = utils.AUTHWriteProviderToRequest(authproviders.FiskerAPIKey, r)
	roles, ok := a.getRolesForProvider(authproviders.FiskerAPIKey, requiredRoles)
	if !ok {
		return nil, errors.New(adminroles.MissingPermissionError)
	}

	err := a.checkAPIToken(roles, token)
	if err != nil {
		return nil, err
	}

	ctx = a.addContext(r.Context(), c.ProviderKey, authproviders.FiskerAPIKey)
	return ctx, nil
}

func (a *AuthAPIToken) HasAPIToken(r *http.Request) (string, bool) {
	token, ok := r.Header[cache.ApiKeyHeader]
	if !ok || len(token[0]) == 0 {
		return "", false
	}

	return token[0], true
}

func (a *AuthAPIToken) checkAPIToken(requiredRoles []adminroles.RoleID, token string) error {
	if len(requiredRoles) == 0 {
		return nil
	}

	roles, err := a.APICache().Get(token)
	if err != nil {
		return err
	}

	checker := adminroles.RolesChecker{}
	checker.SetRequiredRoles(requiredRoles)

	return checker.Check(strings.Split(roles, ","))
}

func (a *AuthAPIToken) GetJWTAuth() *AuthJWTToken {
	a.onceAuthJTW.Do(func() {
		if a.authJWT == nil {
			a.authJWT = &AuthJWTToken{
				groupkey: a.GroupKey,
				apiCalls: a.APICalls,
			}
		}
	})

	return a.authJWT
}

func (a *AuthAPIToken) SetGroupKey(gp string) {
	a.authJWT.SetGroupKey(gp)
}

func (a *AuthAPIToken) APICache() *cache.APITokenCache {
	a.onceCache.Do(func() {
		if a.cache == nil {
			a.cache = &cache.APITokenCache{
				APITokens: a.APITokens,
			}
		}
	})

	return a.cache
}

func (a *AuthAPIToken) GetValidator() jwt.JWTValidatorInterface {
	return a.GetJWTAuth().GetValidator()
}

func (a *AuthAPIToken) Close() {
	a.APITokens = nil
	a.authJWT = nil
	a.cache = nil
}
