/*
 * 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"
	"crypto/rsa"
	"errors"
	"os"

	"github.com/go-chassis/cari/pkg/errsvc"
	"github.com/go-chassis/cari/rbac"
	"github.com/go-chassis/go-archaius"
	"github.com/go-chassis/go-chassis/v2/security/authr"
	"github.com/go-chassis/go-chassis/v2/security/secret"

	"github.com/apache/servicecomb-service-center/pkg/log"
	"github.com/apache/servicecomb-service-center/server/config"
	"github.com/apache/servicecomb-service-center/server/plugin/security/cipher"
)

const (
	RootName     = "root"
	InitPassword = "SC_INIT_ROOT_PASSWORD"
)

var (
	ErrEmptyCurrentPassword = errors.New("current password should not be empty")
	ErrNoPermChangeAccount  = errors.New("can not change other account password")
	ErrWrongPassword        = errors.New("current pwd is wrong")
	ErrSamePassword         = errors.New("the password can not be same as old one")
	ErrNoPrivateKey         = errors.New("read private key failed")

	privateKey *rsa.PrivateKey
)

// Init decide whether enable rbac function and save the build-in roles to db
func Init() {
	if !Enabled() {
		log.Info("rbac is disabled")
		return
	}
	InitResourceMap()
	err := authr.Init()
	if err != nil {
		log.Fatal("can not enable auth module", err)
	}
	migrateOldRoles()
	// build-in role init
	initBuildInRole()
	initBuildInAccount()
	add2WhiteAPIList()
	readPrivateKey()
	readPublicKey()
	log.Info("rbac is enabled")
}

func add2WhiteAPIList() {
	rbac.Add2WhiteAPIList(APITokenGranter)
	rbac.Add2WhiteAPIList("/v4/:project/registry/version", "/version")
	rbac.Add2WhiteAPIList("/v4/:project/registry/health", "/health")

	// user can list self permission without account get permission
	Add2CheckPermWhiteAPIList(APISelfPerms)
	// user can change self password without account modify permission
	Add2CheckPermWhiteAPIList(APIAccountPassword)
}

func initBuildInAccount() {
	accountExist, err := AccountExist(context.Background(), RootName)
	if err != nil {
		log.Fatal("can not enable auth module", err)
	}
	if !accountExist {
		initFirstTime()
	}
}

// read key to memory
func readPrivateKey() {
	pf := config.GetString("rbac.privateKeyFile", "", config.WithStandby("rbac_rsa_private_key_file"))
	// 打开文件
	data, err := os.ReadFile(pf)
	if err != nil {
		log.Fatal("can not read private key", err)
		return
	}
	content := string(data)
	err = archaius.Set("rbac_private_key", content)
	if err != nil {
		log.Fatal("can not init rbac", err)
		return
	}
	privateKeyContent, err := cipher.Decrypt(content)
	if err != nil {
		log.Warn("cipher fallback: " + err.Error())
		privateKeyContent = content
	}
	privateKey, err = secret.ParseRSAPrivateKey(privateKeyContent)
	if err != nil {
		log.Error("can not parse private key", err)
		return
	}
	log.Info("read private key success")
}

// read key to memory
func readPublicKey() {
	pf := config.GetString("rbac.publicKeyFile", "", config.WithStandby("rbac_rsa_public_key_file"))
	// 打开文件
	content, err := os.ReadFile(pf)
	if err != nil {
		log.Fatal("can not find public key", err)
		return
	}
	err = archaius.Set("rbac_public_key", string(content))
	if err != nil {
		log.Fatal("", err)
	}
	log.Info("read public key success")
}
func initFirstTime() {
	//handle root account
	pwd := getPassword()
	if len(pwd) == 0 {
		log.Warn("skip init root account! Cause by " + InitPassword + " is empty. " +
			"Please use the private key to generate a ROOT token and call " + APIAccountList + " create ROOT!")
		return
	}
	a := &rbac.Account{
		Name:     RootName,
		Roles:    []string{rbac.RoleAdmin},
		Password: pwd,
	}
	err := CreateAccount(context.Background(), a)
	if err == nil {
		log.Info("root account init success")
		return
	}
	if errsvc.IsErrEqualCode(err, rbac.ErrAccountConflict) {
		log.Info("root account already exist")
		return
	}
	log.Fatal("can not enable rbac, init root account failed", err)
}

func getPassword() string {
	p := archaius.GetString(InitPassword, "")
	if p == "" {
		return ""
	}
	d, err := cipher.Decrypt(p)
	if err != nil {
		log.Warn("cipher fallback: " + err.Error())
		return p
	}
	return d
}

func Enabled() bool {
	return config.GetRBAC().EnableRBAC
}

// PublicKey get public key to verify a token
func PublicKey() string {
	return archaius.GetString("rbac_public_key", "")
}

// GetPrivateKey return rsa key instance
func GetPrivateKey() (*rsa.PrivateKey, error) {
	if privateKey == nil {
		return nil, ErrNoPrivateKey
	}
	return privateKey, nil
}

// MakeBanKey return ban key
func MakeBanKey(name, ip string) string {
	return name + "::" + ip
}
