package systemtokens

import (
	"errors"
	"fmt"
	"strings"

	"github.com/rancher/rancher/pkg/auth/tokens"
	v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3"
	"github.com/rancher/rancher/pkg/types/config"
	"github.com/rancher/rancher/pkg/types/config/systemtokens"
	"github.com/rancher/wrangler/v3/pkg/randomtoken"
	"github.com/sirupsen/logrus"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func NewSystemTokensFromScale(mgmt *config.ScaledContext) systemtokens.Interface {
	return &systemTokens{
		tokenLister: mgmt.Management.Tokens("").Controller().Lister(),
		tokenClient: mgmt.Management.Tokens(""),
	}
}

type systemTokens struct {
	tokenClient v3.TokenInterface
	tokenLister v3.TokenLister
}

// EnsureSystemToken creates tokens or updates their values if they already exist and returns their value.
// TTL defaults to 1 hour, after that this method will auto-refresh. If your token will be in use for more
// than one hour without calling this method again you must pass in an overrideTTL.
// However, if the overrideTTL is 0 the token will never be cleaned up; this should be done with caution.
func (t *systemTokens) EnsureSystemToken(tokenName, description, kind, username string, overrideTTL *int64, randomize bool) (string, error) {
	var err error
	if !randomize {
		_, err = t.tokenLister.Get("", tokenName)
		if err != nil && !apierrors.IsNotFound(err) {
			return "", err
		}
	}

	// needs fresh token because its missing or expired
	val, err := t.createOrUpdateSystemToken(tokenName, description, kind, username, overrideTTL, randomize)
	if err != nil {
		return "", err
	}

	return val, nil
}

func (t *systemTokens) DeleteToken(tokenName string) error {
	return t.tokenClient.Delete(tokenName, &v1.DeleteOptions{})
}

// Creates token obj with hashed token, returns token. Overwrites if pre-existing.
func (t *systemTokens) createOrUpdateSystemToken(tokenName, description, kind, userName string, overrideTTL *int64, randomize bool) (string, error) {
	if strings.HasPrefix(tokenName, "token-") {
		return "", errors.New("token names can't start with token-")
	}
	key, err := randomtoken.Generate()
	if err != nil {
		return "", errors.New("failed to generate token key")
	}

	if !randomize {
		token, err := t.tokenLister.Get("", tokenName)
		if err != nil {
			if !apierrors.IsNotFound(err) {
				return "", err
			}
		}
		if err == nil {
			if err := t.tokenClient.Delete(token.Name, &v1.DeleteOptions{}); err != nil {
				return "", err
			}
		}
	}

	// not found error, make new token
	token := &v3.Token{
		ObjectMeta: v1.ObjectMeta{
			Name: tokenName,
			Labels: map[string]string{
				tokens.UserIDLabel:    userName,
				tokens.TokenKindLabel: kind,
			},
		},
		TTLMillis:    3600000, // 1 hour, token purge daemon will cleanup
		Description:  description,
		UserID:       userName,
		AuthProvider: "local",
		IsDerived:    true,
		Token:        key,
	}
	if overrideTTL != nil {
		token.TTLMillis = *overrideTTL
	}
	if randomize {
		token.ObjectMeta.Name = ""
		token.ObjectMeta.GenerateName = tokenName
	}

	err = tokens.ConvertTokenKeyToHash(token)
	if err != nil {
		return "", err
	}
	logrus.Infof("Creating system token for %v, token: %v", userName, tokenName)
	token, err = t.tokenClient.Create(token)
	if err != nil {
		return "", err
	}

	fullVal := fmt.Sprintf("%s:%s", token.Name, key)
	return fullVal, nil
}
