/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package rbac

import (
	"context"
	"fmt"

	"github.com/apache/servicecomb-service-center/pkg/log"
	"github.com/apache/servicecomb-service-center/pkg/privacy"
	"github.com/apache/servicecomb-service-center/pkg/util"
	"github.com/apache/servicecomb-service-center/server/service/validator"
	"github.com/go-chassis/cari/discovery"
	"github.com/go-chassis/cari/rbac"
)

func ChangePassword(ctx context.Context, a *rbac.Account) error {
	err := validator.ValidateChangePWD(a)
	if err != nil {
		return discovery.NewError(discovery.ErrInvalidParams, err.Error())
	}

	changer, err := AccountFromContext(ctx)
	if err != nil {
		return discovery.NewError(discovery.ErrInternal, err.Error())
	}

	// change self password, need to check password mismatch
	if changer.Name == a.Name {
		return changePassword(ctx, a.Name, a.CurrentPassword, a.Password)
	}

	// change other user's password, only admin role can do this and no need
	// supply current password
	for _, r := range changer.Roles {
		if r == rbac.RoleAdmin {
			return changePasswordForcibly(ctx, a.Name, a.Password)
		}
	}

	// other cases, change password is forbidden
	return discovery.NewError(discovery.ErrForbidden, ErrNoPermChangeAccount.Error())
}

func changePasswordForcibly(ctx context.Context, name, pwd string) error {
	old, err := GetAccount(ctx, name)
	if err != nil {
		log.Error("can not change pwd", err)
		return err
	}
	return doChangePassword(ctx, old, pwd)
}
func changePassword(ctx context.Context, name, currentPassword, pwd string) error {
	if currentPassword == "" {
		log.Error("current pwd is empty", nil)
		return discovery.NewError(discovery.ErrInvalidParams, ErrEmptyCurrentPassword.Error())
	}
	ip := util.GetIPFromContext(ctx)
	if IsBanned(MakeBanKey(name, ip)) {
		log.Warn(fmt.Sprintf("ip [%s] is banned, account: %s", ip, name))
		return ErrAccountBlocked
	}
	if currentPassword == pwd {
		return rbac.NewError(rbac.ErrNewPwdBad, ErrSamePassword.Error())
	}
	old, err := GetAccount(ctx, name)
	if err != nil {
		log.Error("can not change pwd", err)
		return err
	}
	same := privacy.SamePassword(old.Password, currentPassword)
	if !same {
		log.Error("current password is wrong", nil)
		TryLockAccount(MakeBanKey(name, ip))
		return ErrOldPwdWrong
	}
	return doChangePassword(ctx, old, pwd)
}

func doChangePassword(ctx context.Context, old *rbac.Account, pwd string) error {
	var err error
	old.Password, err = privacy.ScryptPassword(pwd)
	if err != nil {
		log.Error("encrypt password failed", err)
		return err
	}
	err = EditAccount(ctx, old)
	if err != nil {
		log.Error("can not change pwd", err)
		return err
	}
	return nil
}
