/*
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"
	"errors"
	"log"
	"strings"

	"code.vereign.com/code/viam-apis/clientutils"
	"github.com/golang/protobuf/proto"

	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"
	"code.vereign.com/code/viam-apis/utils"
	"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
	VereignPrivateKeyPEM []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()

	getKeyResponse := &api.GetKeyResponse{}

	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.Key = key
		return getKeyResponse, nil
	}

	if in.KeyType == api.KeyType_KT_EMPTY {
		getKeyResponse.StatusList = utils.AddStatus(getKeyResponse.StatusList,
			"400", api.StatusType_ERROR, "KeyType cannot be empty")
		return getKeyResponse, nil
	}

	data, err := client.DoGetDataCall("keys", in.Uuid+"/"+api.KeyType.String(in.KeyType))
	if err != nil {
		log.Printf("grpc call DoGetDataCall to DataStorage failed: %s", err)
		getKeyResponse.Key = nil
		getKeyResponse.StatusList = utils.AddStatus(getKeyResponse.StatusList,
			"500", api.StatusType_ERROR, err.Error())
		return getKeyResponse, nil
	}

	if data.Errors != "" {
		getKeyResponse.Key = nil
		getKeyResponse.StatusList = utils.AddStatus(getKeyResponse.StatusList,
			"500", api.StatusType_ERROR, data.Errors)
	} else {
		key := &api.Key{}
		proto.Unmarshal(data.Data.Data, key)
		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()

	setKeyResponse := &api.SetKeyResponse{}

	if in.Uuid == "root" {
		setKeyResponse.StatusList = utils.AddStatus(setKeyResponse.StatusList,
			"400", api.StatusType_ERROR, "Can not set root CA keys")
		return setKeyResponse, nil
	}

	if in.KeyType == api.KeyType_KT_EMPTY {
		setKeyResponse.StatusList = utils.AddStatus(setKeyResponse.StatusList,
			"400", api.StatusType_ERROR, "KeyType cannot be empty")
		return setKeyResponse, nil
	}

	data, err := client.DoGetDataCall("keys", in.Uuid+"/"+api.KeyType.String(in.KeyType))
	if err != nil {
		log.Printf("grpc call DoGetDataCall to DataStorage failed: %s", err)
		setKeyResponse.StatusList = utils.AddStatus(setKeyResponse.StatusList,
			"500", api.StatusType_ERROR, err.Error())
		return setKeyResponse, nil
	}

	if data.Errors != "" {
		setKeyResponse.StatusList = utils.AddStatus(setKeyResponse.StatusList,
			"400", api.StatusType_ERROR, data.Errors)
		return setKeyResponse, nil
	}

	key := &api.Key{}
	proto.Unmarshal(data.Data.Data, key)
	if key != nil && key.Content != nil && len(key.Content) > 0 {
		setKeyResponse.StatusList = utils.AddStatus(setKeyResponse.StatusList,
			"400", api.StatusType_ERROR, "Key is already set")
		return setKeyResponse, nil
	}

	result, errors, err := client.PutData("keys", in.Uuid+"/"+api.KeyType.String(in.KeyType), in.Key)
	setKeyResponse.StatusList = keyutils.HandlePutDataErrors(setKeyResponse.StatusList, errors, err)

	if setKeyResponse.StatusList == nil || len(setKeyResponse.StatusList) == 0 {
		setKeyResponse.StatusList = utils.AddStatus(setKeyResponse.StatusList,
			"200", api.StatusType_INFO, result)
	}

	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 DoPutDataCallWithString: %s", err)
			return nil, err
		}
	}

	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()

	reserveKeyUUIDResponse := &api.ReserveKeyUUIDResponse{}

	uuid, err := keyutils.GenerateUnusedUUID(client)
	if err != nil {
		log.Printf("Error: %v", err)
		reserveKeyUUIDResponse.StatusList = utils.AddStatus(reserveKeyUUIDResponse.StatusList,
			"500", api.StatusType_INFO, err.Error())
	}

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

	result, errors, err := client.PutData("keys", uuid+"/"+api.KeyType.String(api.KeyType_PRIVATE), emptyKey)
	reserveKeyUUIDResponse.StatusList = keyutils.HandlePutDataErrors(reserveKeyUUIDResponse.StatusList, errors, err)

	if reserveKeyUUIDResponse.StatusList == nil || len(reserveKeyUUIDResponse.StatusList) == 0 {
		result, errors, err = client.PutData("keys", uuid+"/"+api.KeyType.String(api.KeyType_PUBLIC), emptyKey)
		reserveKeyUUIDResponse.StatusList = keyutils.HandlePutDataErrors(reserveKeyUUIDResponse.StatusList, errors, err)
	}

	if reserveKeyUUIDResponse.StatusList == nil || len(reserveKeyUUIDResponse.StatusList) == 0 {
		result, errors, err = client.PutData("keys", uuid+"/"+api.KeyType.String(api.KeyType_CERTIFICATE), emptyKey)
		reserveKeyUUIDResponse.StatusList = keyutils.HandlePutDataErrors(reserveKeyUUIDResponse.StatusList, errors, err)
	}

	if reserveKeyUUIDResponse.StatusList == nil || len(reserveKeyUUIDResponse.StatusList) == 0 {
		reserveKeyUUIDResponse.Uuid = uuid
		reserveKeyUUIDResponse.StatusList = utils.AddStatus(reserveKeyUUIDResponse.StatusList,
			"200", api.StatusType_INFO, result)
	}

	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{}

	response, err := dataStorageClient.DoGetDataCall(keyToKeyIdTable, in.PublicKey)
	if err == nil && response.Data != nil && response.Data.Data != nil {
		keyID := string(response.Data.Data)
		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{}

		data, err := dataStorageClient.DoGetDataCall("keys", checkID+"/"+api.KeyType_PUBLIC.String())

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

		if data.Errors != "" {
			return nil, errors.New(data.Errors)
		} else {
			proto.Unmarshal(data.Data.Data, key)
		}

		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 DoPutDataCallWithString: %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, Errors: ""}, nil
}