package httphandlers

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

	"github.com/fiskerinc/cloud-services/pkg/adminroles"
	"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 errNoUsername = errors.New("no username")

type AuthCheckerInterface interface {
	GetHandler(requiredRoles map[string][]adminroles.RoleID, next http.HandlerFunc) http.HandlerFunc
	Check(requiredRoles map[string][]adminroles.RoleID, r *http.Request) (context.Context, error)
	GetValidator() jwt.JWTValidatorInterface
	SetGroupKey(string)
	Close()
}

type AuthJWTToken struct {
	groupkey  string
	apiCalls  queries.APICallsInterface
	validator jwt.JWTValidatorInterface
	AuthBase
}

func (a *AuthJWTToken) GetValidator() jwt.JWTValidatorInterface {
	if a.validator == nil {
		a.validator = jwt.NewJWTValidator("")
	}
	return a.validator
}

func (a *AuthJWTToken) GroupKey() string {
	if len(a.groupkey) == 0 {
		return "custom:groups"
	}

	return a.groupkey
}

func (a *AuthJWTToken) SetGroupKey(gp string) {
	a.groupkey = gp
}

func (a *AuthJWTToken) GetHandler(requiredRoles map[string][]adminroles.RoleID, next http.HandlerFunc) http.HandlerFunc {
	wrapper := func(w http.ResponseWriter, r *http.Request) {
		ctx, err := a.Check(requiredRoles, r)
		if err != nil {
			logger.Warn().Msgf("AuthJWTToken %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 *AuthJWTToken) 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, err := jwt.GetAuthorizationHeader(r)
	if err != nil {
		return nil, err
	}

	payload, err := a.GetValidator().ValidateToken(token.Token)
	if err != nil {
		return nil, err
	}

	if username, ok := a.getUsername(payload); ok && username != "" {
		ctx := context.WithValue(r.Context(), ClientIDContextKey, username)
		*r = *r.WithContext(ctx)
	} else {
		return nil, errNoUsername
	}

	provider := a.getProvider(payload)
	roles, ok := a.getRolesForProvider(provider, requiredRoles)
	if !ok {
		return nil, errors.New(adminroles.MissingPermissionError)
	}

	checker := adminroles.RolesChecker{}
	checker.SetRequiredRoles(roles)
	err = checker.CheckGroups(payload[a.GroupKey()])
	if err != nil {
		return nil, err
	}

	ctx := a.addContext(r.Context(), c.ProviderKey, provider)

	return ctx, nil
}

func (a *AuthJWTToken) getProvider(payload map[string]interface{}) string {
	var ok bool
	var data interface{}
	var identities []interface{}
	var identity map[string]interface{}
	var provider string

	if data, ok = payload["identities"]; !ok {
		return authproviders.Default
	}

	if identities, ok = data.([]interface{}); !ok || len(identities) != 1 {
		return authproviders.Default
	}

	if identity, ok = identities[0].(map[string]interface{}); !ok {
		return authproviders.Default
	}

	if provider, ok = identity["providerName"].(string); !ok {
		return authproviders.Default
	}

	return provider

}

func (a *AuthJWTToken) Close() {
	// nothing to dispose here
}

func (a *AuthJWTToken) getUsername(payload map[string]interface{}) (string, bool) {
	username, ok := payload["cognito:username"].(string)
	if ok && username != "" {
		return username, true
	}

	username, ok = payload["username"].(string)
	if ok && username != "" {
		return username, true
	}

	username, ok = payload["preferred_username"].(string)
	if ok && username != "" {
		return username, true
	}

	username, ok = payload["email"].(string)
	if ok && username != "" {
		return username, true
	}

	return "", false
}
