
// createProfiles provides a function to create user profiles

package createProfiles

import "seekia/internal/appValues"
import "seekia/internal/cryptography/blake3"
import "seekia/internal/cryptography/edwardsKeys"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/profiles/profileFormat"
import "seekia/internal/profiles/readProfiles"

import messagepack "github.com/vmihailenco/msgpack/v5"

import "strings"
import "time"
import "errors"


func CreateProfile(identityPublicKey [32]byte, identityPrivateKey [64]byte, inputProfileMap map[string]string)([]byte, error){

	networkTypeString, exists := inputProfileMap["NetworkType"]
	if (exists == false){
		return nil, errors.New("CreateProfile called with profileMap missing NetworkType")
	}

	networkType, err := helpers.ConvertNetworkTypeStringToByte(networkTypeString)
	if (err != nil){
		return nil, errors.New("CreateProfile called with profileMap containing invalid NetworkType: " + networkTypeString)
	}

	profileType, exists := inputProfileMap["ProfileType"]
	if (exists == false) {
		return nil, errors.New("CreateProfile called with profile map missing profile type.")
	}

	if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){
		return nil, errors.New("CreateProfile called with profile map containing invalid profileType: " + profileType)
	}

	networkTypeEncoded, err := encoding.EncodeMessagePackBytes(networkType)
	if (err != nil){ return nil, err }

	profileTypeEncoded, err := encoding.EncodeMessagePackBytes(profileType)
	if (err != nil) { return nil, err }

	profileVersion, err := appValues.GetProfileVersion(profileType)
	if (err != nil) { return nil, err }

	profileVersionEncoded, err := encoding.EncodeMessagePackBytes(profileVersion)
	if (err != nil) { return nil, err }

	identityPublicKeyEncoded, err := encoding.EncodeMessagePackBytes(identityPublicKey)
	if (err != nil) { return nil, err }
	
	creationTime := time.Now().Unix()

	creationTimeEncoded, err := encoding.EncodeMessagePackBytes(creationTime)
	if (err != nil) { return nil, err }

	profileContentMap := map[int]messagepack.RawMessage{
		51: networkTypeEncoded,
		1: profileVersionEncoded,
		2: identityPublicKeyEncoded,
		3: profileTypeEncoded,
		4: creationTimeEncoded,
	}

	getProfileIsDisabledBool := func()bool{

		iAmDisabled, exists := inputProfileMap["Disabled"]
		if (exists == true && iAmDisabled == "Yes"){
			return true
		}
		return false
	}

	profileIsDisabled := getProfileIsDisabledBool()
	if (profileIsDisabled == true){

		trueEncoded, err := encoding.EncodeMessagePackBytes(true)
		if (err != nil) { return nil, err }

		profileContentMap[5] = trueEncoded
	}

	if (profileIsDisabled == false){

		for attributeName, attributeValue := range inputProfileMap{

			if (attributeName == ""){
				return nil, errors.New("CreateProfile called with profileMap containing empty key.")
			}
			if (attributeValue == ""){
				return nil, errors.New("CreateProfile called with profileMap containing empty value.")
			}

			if (attributeName == "CreationTime" || attributeName == "ProfileVersion"){
				// This function should be called with profile maps which do not contain these attributes.
				return nil, errors.New("CreateProfile called with profileMap containing unwanted attribute: " + attributeName)	
			}

			if (attributeName == "NetworkType" || attributeName == "ProfileType" || attributeName == "Disabled"){
				// These are attributes we already added to the profileContentMap
				continue
			}

			getAttributeEncodedBytes := func()(messagepack.RawMessage, error){

				if (attributeName == "NaclKey" || attributeName == "MlkemKey"){

					// We encode the raw bytes of the keys to save space

					keyBytes, err := encoding.DecodeBase64StringToBytes(attributeValue)
					if (err != nil){
						return nil, errors.New("CreateProfile called with profileMap containing invalid " + attributeName + ": " + attributeValue)
					}

					keyBytesEncoded, err := encoding.EncodeMessagePackBytes(keyBytes)
					if (err != nil){ return nil, err }

					return keyBytesEncoded, nil
				}
				if (attributeName == "Photos"){

					// We encode the raw bytes a list of []byte

					base64PhotosList := strings.Split(attributeValue, "+")

					rawPhotoBytesList := make([][]byte, 0, len(base64PhotosList))

					for _, base64Photo := range base64PhotosList{

						base64Bytes, err := encoding.DecodeBase64StringToBytes(base64Photo)
						if (err != nil) {
							return nil, errors.New("CreateProfile called with profile containing invalid photos attribute: Item not Base64: " + base64Photo)
						}
						rawPhotoBytesList = append(rawPhotoBytesList, base64Bytes)
					}

					rawPhotoBytesListEncoded, err := encoding.EncodeMessagePackBytes(rawPhotoBytesList)
					if (err != nil){ return nil, err }

					return rawPhotoBytesListEncoded, nil
				}

				//TODO: Add more attributes
				// We can encode all numbers and hex-encoded strings using messagepack to reduce size

				// //TODO: Encode base pairs using a number
				// There are 36 total base pair possibilities
				// This will reduce the base pair encoded size by 1/3
				// This will be significant once each profile has thousands of base pairs

				// We encode the attribute value as a string

				attributeEncoded, err := encoding.EncodeMessagePackBytes(attributeValue)
				if (err != nil){ return nil, err }

				return attributeEncoded, nil	
			}

			attributeEncoded, err := getAttributeEncodedBytes()
			if (err != nil) { return nil, err }

			attributeIdentifier, err := profileFormat.GetAttributeIdentifierFromAttributeName(attributeName)
			if (err != nil){
				return nil, errors.New("CreateProfile failed: " + err.Error())
			}

			profileContentMap[attributeIdentifier] = attributeEncoded
		}
	}

	profileContentMessagepack, err := encoding.EncodeMessagePackBytes(profileContentMap)
	if (err != nil) {
		return nil, errors.New("CreateProfile failed: " + err.Error())
	}

	contentHash, err := blake3.Get32ByteBlake3Hash(profileContentMessagepack)
	if (err != nil) { return nil, err }

	profileSignature := edwardsKeys.CreateSignature(identityPrivateKey, contentHash)

	signatureEncoded, err := encoding.EncodeMessagePackBytes(profileSignature)
	if (err != nil) { return nil, err }

	profileSlice := []messagepack.RawMessage{signatureEncoded, profileContentMessagepack}

	profileBytes, err := encoding.EncodeMessagePackBytes(profileSlice)
	if (err != nil) { return nil, err }

	profileIsValid, err := readProfiles.VerifyProfile(profileBytes)	
	if (err != nil){ return nil, err }
	if (profileIsValid == false){
		return nil, errors.New("CreateProfile failed: Invalid result profile.")
	}

	return profileBytes, nil
}



