package userpassword

import (
	"context"
	gerror "errors"
	"fmt"
	"github.com/jinzhu/gorm"
	"github.com/pingcap/tep/db/entity"
	"github.com/pingcap/tep/pkg/apis/apiserver"
	"github.com/pingcap/tep/pkg/rbac"
	store "github.com/pingcap/tep/pkg/registry/apiserver"
	"github.com/pingcap/tep/pkg/utils"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apiserver/pkg/registry/rest"
	"strings"
)

type Storage struct {
	store         *store.Store
	groupResource schema.GroupResource
}

func NewStorage(store *store.Store, groupResource schema.GroupResource) *Storage {
	return &Storage{
		store:         store,
		groupResource: groupResource,
	}
}

func (s *Storage) Kind() string {
	return "UserPassword"
}

func (s *Storage) New() runtime.Object {
	return &apiserver.UpdatePassword{}
}

func (s *Storage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	operatorName := utils.GetAPIUsername(ctx)
	if operatorName != name && !rbac.CanOperate(s.store.DB, operatorName, rbac.UserOperation) {
		return nil, false, errors.NewForbidden(s.groupResource, name, gerror.New("As a regular user, you can only change your own password. "))
	}
	newObj, err := objInfo.UpdatedObject(ctx, nil)
	if err != nil {
		return nil, false, err
	}
	updatePassword := newObj.(*apiserver.UpdatePassword)
	if strings.TrimSpace(updatePassword.NewPassword) == "" {
		return nil, false, errors.NewBadRequest("invalid password")
	}
	var dbUser entity.User
	if err := s.store.DB.Where("username=?", name).First(&dbUser).Error; err == gorm.ErrRecordNotFound {
		return nil, false, errors.NewNotFound(s.groupResource, fmt.Sprintf("user named %s is not found", name))
	}
	if !utils.CheckPasswordHash(updatePassword.CurrentPassword, dbUser.Password) {
		return nil, false, errors.NewBadRequest("user current password is wrong")
	}
	dbUser.Password, _ = utils.HashPassword(updatePassword.NewPassword)
	if err := s.store.DB.Save(dbUser).Error; err != nil {
		return nil, false, errors.NewInternalError(err)
	}
	return newObj, true, nil
}

func (s *Storage) NamespaceScoped() bool {
	return false
}
