/*
Copyright (c) 2018 Vereign AG [https://www.vereign.com]

This is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package handler

import (
	"encoding/base64"
	"strings"

	"code.vereign.com/code/viam-apis/errors"

	"code.vereign.com/code/viam-apis/log"

	"code.vereign.com/code/viam-apis/clientutils"

	"code.vereign.com/code/key-storage-agent/config"
	keyutils "code.vereign.com/code/key-storage-agent/utils"
	"code.vereign.com/code/viam-apis/authentication"
	"code.vereign.com/code/viam-apis/key-storage-agent/api"
	"golang.org/x/net/context"
	"google.golang.org/grpc/metadata"
)

const (
	keyToKeyIdTable = "authenticationKeyToKeyId"
)

// Server represents the gRPC server
type KeyStorageServerImpl struct {
	DataStorageUrl string
	CertPEM        []byte
	KeyPEM         []byte
	CaCertPEM      []byte
	VereignCertPEM []byte
	MaxMessageSize int
}

var version = "undefined"

func (s *KeyStorageServerImpl) CreateAuthentication(ctx context.Context) *authentication.Authentication {
	if md, ok := metadata.FromIncomingContext(ctx); ok {
		uuid := strings.Join(md["uuid"], "")
		session := strings.Join(md["session"], "")

		auth := &authentication.Authentication{
			Uuid:    uuid,
			Session: session,
		}

		return auth
	}

	return nil
}

func (s *KeyStorageServerImpl) GetKey(ctx context.Context, in *api.GetKeyRequest) (*api.GetKeyResponse, error) {
	auth := s.CreateAuthentication(ctx)

	client := keyutils.CreateDataStorageClient(auth)
	defer client.CloseClient()

	if in.KeyType == api.KeyType_CERTIFICATE && in.Uuid == "root" {
		key := &api.Key{}
		/*data, err := ioutil.ReadFile(s.VereignCertFilePath)

		if err != nil {
			log.Printf("Error: %v", err)
			getKeyResponse.StatusList = utils.AddStatus(getKeyResponse.StatusList,
				"400", api.StatusType_ERROR, "Can not get root certificate")
			return getKeyResponse, nil
		}*/

		key.Content = s.VereignCertPEM
		key.Revoked = false

		getKeyResponse := &api.GetKeyResponse{Key: key}
		return getKeyResponse, nil
	}

	if in.KeyType == api.KeyType_KT_EMPTY {
		return nil, errors.NewUser("KeyType cannot be empty")
	}

	key := &api.Key{}

	hasData, _, err := client.GetData("keys", in.Uuid+"/"+api.KeyType.String(in.KeyType), key)
	if err != nil {
		log.Printf("grpc call GetData to DataStorage failed: %s", err)
		return nil, err
	}

	if !hasData {
		log.Println("No such key " + in.Uuid)
		return nil, errors.NewUser("No such key " + in.Uuid)
	}

	getKeyResponse := &api.GetKeyResponse{Key: key}
	return getKeyResponse, nil
}

func (s *KeyStorageServerImpl) SetKey(ctx context.Context, in *api.SetKeyRequest) (*api.SetKeyResponse, error) {
	auth := s.CreateAuthentication(ctx)

	client := keyutils.CreateDataStorageClient(auth)
	defer client.CloseClient()

	if in.Uuid == "root" {
		return nil, errors.NewUser("Can not set root CA keys")
	}

	if in.KeyType == api.KeyType_KT_EMPTY {
		return nil, errors.NewUser("KeyType cannot be empty")
	}

	key := &api.Key{}

	if config.Config.ReplaceKey == false {
		_, _, err := client.GetData("keys", in.Uuid+"/"+api.KeyType.String(in.KeyType), key)
		if err != nil {
			log.Printf("grpc call GetData to DataStorage failed: %s", err)
			return nil, errors.WrapInternalFormat(err, "grpc call GetData to DataStorage failed: %s", err)
		}

		if len(key.Content) > 0 {
			return nil, errors.NewUser("Key is already set")
		}
	}

	_, _, err := client.PutData("keys", in.Uuid+"/"+api.KeyType.String(in.KeyType), in.Key)
	if err != nil {
		return nil, err
	}

	if in.KeyType == api.KeyType_PUBLIC {
		keyContent := base64.StdEncoding.EncodeToString(in.Key.Content)

		_, _, err = client.PutString(keyToKeyIdTable, keyContent, in.Uuid)
		if err != nil {
			log.Printf("can't PutString: %s", err)
			return nil, errors.WrapInternal(err, "can't PutString")
		}
	}

	setKeyResponse := &api.SetKeyResponse{}
	return setKeyResponse, nil
}

func (s *KeyStorageServerImpl) ReserveKeyUUID(ctx context.Context, in *api.ReserveKeyUUIDRequest) (*api.ReserveKeyUUIDResponse, error) {
	auth := s.CreateAuthentication(ctx)

	client := keyutils.CreateDataStorageClient(auth)
	defer client.CloseClient()

	uuid, err := keyutils.GenerateUnusedUUID(client)
	if err != nil {
		log.Printf("Error: %v", err)
		return nil, err
	}

	emptyKey := &api.Key{
		Content: []byte{},
	}

	_, _, err = client.PutData("keys", uuid+"/"+api.KeyType.String(api.KeyType_PRIVATE), emptyKey)
	if err != nil {
		return nil, errors.WrapInternalFormat(err, "Could not store key %s", uuid+"/"+api.KeyType.String(api.KeyType_PRIVATE))
	}

	_, _, err = client.PutData("keys", uuid+"/"+api.KeyType.String(api.KeyType_PUBLIC), emptyKey)
	if err != nil {
		return nil, errors.WrapInternalFormat(err, "Could not store key %s", uuid+"/"+api.KeyType.String(api.KeyType_PUBLIC))
	}

	_, _, err = client.PutData("keys", uuid+"/"+api.KeyType.String(api.KeyType_CERTIFICATE), emptyKey)
	if err != nil {
		return nil, errors.WrapInternalFormat(err, "Could not store key %s", uuid+"/"+api.KeyType.String(api.KeyType_CERTIFICATE))
	}

	reserveKeyUUIDResponse := &api.ReserveKeyUUIDResponse{Uuid: uuid}
	return reserveKeyUUIDResponse, nil
}

func (s *KeyStorageServerImpl) GetKeyId(ctx context.Context, in *api.GetKeyIdByKeyRequest) (*api.GetKeyIdByKeyResponse, error) {
	auth := s.CreateAuthentication(ctx)
	//in.PublicKey
	entitiesMagamentClient := keyutils.CreateEntitiesManagementClient(auth)
	defer entitiesMagamentClient.CloseClient()

	dataStorageClient := keyutils.CreateDataStorageClient(auth)
	defer dataStorageClient.CloseClient()

	keyIdResponse := &api.GetKeyIdByKeyResponse{}

	keyId, errorsString, err := dataStorageClient.GetString(keyToKeyIdTable, in.PublicKey)
	if err == nil && errorsString == "" && keyId != "" {
		keyIdResponse.KeyId = keyId
		return keyIdResponse, nil
	} else {
		if err != nil {
			return nil, err
		} else {
			return nil, errors.New("Can not get public key")
		}
	}

	entity, err := clientutils.GetLastEntity(entitiesMagamentClient, in.EntityUUID)

	if err != nil {
		return nil, err
	}

	for _, checkID := range entity.AuthenticationKeys {
		key := &api.Key{}

		hasData, errorsString, err := dataStorageClient.GetData("keys", checkID+"/"+api.KeyType_PUBLIC.String(), key)

		if err != nil {
			log.Printf("grpc call GetData to DataStorage failed: %s", err)
			return nil, err
		}

		if errorsString != "" {
			log.Printf("Error: %s", errorsString)
			return nil, errors.New(errorsString)
		}

		if !hasData {
			log.Println("No such checkID " + checkID)
			return nil, errors.New("No such checkID " + checkID)
		}

		keyFromStorage := base64.StdEncoding.EncodeToString(key.Content)

		if in.PublicKey != keyFromStorage {
			continue
		}

		_, _, err = dataStorageClient.PutString(keyToKeyIdTable, in.PublicKey, checkID)
		if err != nil {
			log.Printf("can't PutString: %s", err)

			return nil, err
		}

		keyIdResponse.KeyId = checkID
		return keyIdResponse, nil
	}

	//getKeyFromContent
	return keyIdResponse, nil
}

func (s *KeyStorageServerImpl) GetVersionKSA(ctx context.Context, in *api.GetVersionKSAMessage) (*api.GetVersionKSAResponseMessage, error) {
	log.Println("Version: " + version)
	return &api.GetVersionKSAResponseMessage{Version: version}, nil
}