/*
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 utils

import (
	"crypto/rand"
	"crypto/x509"
	"fmt"
	"io"

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

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

	"encoding/pem"
	"io/ioutil"

	"code.vereign.com/code/key-storage-agent/config"
	"code.vereign.com/code/viam-apis/authentication"
	"code.vereign.com/code/viam-apis/clientutils"
	"code.vereign.com/code/viam-apis/data-storage-agent/client"
	dsclient "code.vereign.com/code/viam-apis/data-storage-agent/client"
	emclient "code.vereign.com/code/viam-apis/entities-management-agent/client"
	"code.vereign.com/code/viam-apis/key-storage-agent/api"
)

var ErrNoData = errors.New("No data")

func GenerateUnusedUUID(client *client.DataStorageClientImpl) (string, error) {
	count := 0
	for {
		uuid, err := NewUUID()

		// check that uuid is not used
		key := &api.Key{}

		hasData, _, err := client.GetData("keys", uuid+"/"+api.KeyType.String(api.KeyType_PRIVATE), key)
		if err != nil {
			return "", errors.WrapInternal(err, "Could not generate unused UUID")
		}
		if !hasData {
			return uuid, nil
		}
		if count >= 10 {
			return "", errors.New("Could not generate unused UUID in 10 tries")
		}
		count++
	}
}

//TODO create one single function to create UUIDs
func NewUUID() (string, error) {
	uuid := make([]byte, 16)
	n, err := io.ReadFull(rand.Reader, uuid)
	if n != len(uuid) || err != nil {
		return "", err
	}

	uuid[8] = uuid[8]&^0xc0 | 0x80
	uuid[6] = uuid[6]&^0xf0 | 0x40

	return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
}

//func HandlePutDataErrors(statusList []*api.Status, errors string, err error) []*api.Status {
//	if err != nil {
//		log.Printf("Error: %v", err)
//		statusList = utils.AddStatus(statusList, "500", api.StatusType_ERROR, err.Error())
//	} else if errors != "" {
//		statusList = utils.AddStatus(statusList, "400", api.StatusType_ERROR, errors)
//	}
//
//	return statusList
//}

func ReadCertificateFromPEM(pemString []byte) (*x509.Certificate, error) {
	certificatePemBlock, err := ReadPemBlockFromBytes(pemString)
	if err != nil {
		log.Printf("Error: %v", err)
		return nil, err
	}

	certificate, err := x509.ParseCertificate(certificatePemBlock.Bytes)
	if err != nil {
		log.Printf("Error: %v", err)
		return nil, err
	}

	return certificate, nil
}

func ReadPemBlockFromBytes(pemString []byte) (*pem.Block, error) {
	fileBytes := pemString

	certificatePemBlock, _ := pem.Decode(fileBytes)

	return certificatePemBlock, nil
}

func ReadCertificateFromFile(fileName string) (*x509.Certificate, error) {
	certificatePemBlock, err := ReadPemBlockFromFile(fileName)
	if err != nil {
		log.Printf("Error: %v", err)
		return nil, err
	}

	certificate, err := x509.ParseCertificate(certificatePemBlock.Bytes)
	if err != nil {
		log.Printf("Error: %v", err)
		return nil, err
	}

	return certificate, nil
}

func ReadPemBlockFromFile(fileName string) (*pem.Block, error) {
	fileBytes, err := ioutil.ReadFile(fileName)
	if err != nil {
		log.Printf("Error: %v", err)
		return nil, err
	}

	certificatePemBlock, _ := pem.Decode(fileBytes)

	return certificatePemBlock, nil
}

func GetKey(client *client.DataStorageClientImpl, uuid string, keyType api.KeyType) (*api.Key, error) {
	key := &api.Key{}

	hasData, _, err := client.GetData("keys", uuid+"/"+api.KeyType.String(keyType), key)
	if err != nil {
		return nil, errors.WrapInternalFormat(err, "Could not get key %s", uuid+"/"+api.KeyType.String(keyType))
	}

	if !hasData {
		return nil, errors.WrapUserFormat(ErrNoData, "Could not get key %s", uuid+"/"+api.KeyType.String(keyType))
	}

	return key, nil
}

func CreateDataStorageClient(auth *authentication.Authentication) *dsclient.DataStorageClientImpl {
	return clientutils.CreateDataStorageClient(auth, config.DataStorageUrl, config.UseTLS, config.CertificatePEM,
		config.PrivateKeyPEM, config.CaCertificatePEM, config.MaxMessageSize)
}

func CreateEntitiesManagementClient(auth *authentication.Authentication) *emclient.EntitiesManagerClientImpl {
	return clientutils.CreateEntitiesManagementClient(auth, config.EntitiesManagerUrl, config.UseTLS, config.CertificatePEM,
		config.PrivateKeyPEM, config.CaCertificatePEM, config.MaxMessageSize)
}