package auth0

import (
	"fmt"

	"github.com/form3tech-oss/jwt-go"
	"github.com/juju/errors"
)

type AuthValidatorID string

const (
	AuthValidatorIDCentral    AuthValidatorID = "central"
	AuthValidatorIDManagement AuthValidatorID = "management"
)

type validatorOption struct {
	id          AuthValidatorID
	audience    string
	m2mAudience string
	secret      []byte
	jwks        Jwks
}

type RawTokenValidator struct {
	validators []validatorOption
	ids        map[AuthValidatorID]int
}

func NewRawTokenValidator() *RawTokenValidator {
	return &RawTokenValidator{
		validators: make([]validatorOption, 0),
		ids:        make(map[AuthValidatorID]int),
	}
}

func (v *RawTokenValidator) RegisterAuth(id AuthValidatorID, conf Conf, jwks Jwks) *RawTokenValidator {
	if _, exist := v.ids[id]; exist {
		// If id exists, return silently.
		return v
	}
	idx := len(v.validators)
	v.validators = append(v.validators, validatorOption{
		id:          id,
		audience:    conf.Audience,
		m2mAudience: conf.ClientCredentialAudience,
		secret:      conf.Secret,
		jwks:        jwks,
	})
	v.ids[id] = idx
	return v
}

// Validate validates the raw token against a specified auth configurations.
func (v *RawTokenValidator) ValidateFor(id AuthValidatorID, rawToken string) (jwt.MapClaims, error) {
	idx, ok := v.ids[id]
	if !ok {
		return nil, fmt.Errorf("auth validator %s not found", id)
	}
	validator := v.validators[idx]
	claims, err := ParseToken(validator.secret, validator.jwks, rawToken)
	if err != nil {
		return nil, errors.Annotate(err, "failed to parse auth token")
	}
	var matchAudience string
	if claims["gty"] == ClientCredentialsAuthType {
		matchAudience = validator.m2mAudience
	} else {
		matchAudience = validator.audience
	}
	if !claims.VerifyAudience(matchAudience, false) {
		return nil, errors.New("invalid audience in the auth token")
	}
	if err := claims.Valid(); err != nil {
		return nil, errors.Annotate(err, "invalid claims")
	}
	return claims, nil
}

// Validate validates the raw token against all registered auth configurations.
func (v *RawTokenValidator) ValidateForMulti(ids []AuthValidatorID, rawToken string) (AuthValidatorID, jwt.MapClaims, error) {
	var err error
	for _, id := range ids {
		claims, err := v.ValidateFor(id, rawToken)
		if err == nil {
			return id, claims, nil
		}
	}
	if err != nil {
		return "", nil, err
	}
	return "", nil, errors.New("no matching auth validator")
}
