
// createMessages provides functions to create Seekia chat messages
// The chat message structure will eventually be documented in /documentation/Specification.md

package createMessages

//TODO: Add an upper limit on bytes, and an upper limit for size of communication.
//TODO: Verify all message values and function inputs.

import "seekia/internal/allowedText"
import "seekia/internal/appValues"
import "seekia/internal/cryptography/blake3"
import "seekia/internal/cryptography/chaPolyShrink"
import "seekia/internal/cryptography/edwardsKeys"
import "seekia/internal/cryptography/csidh"
import "seekia/internal/cryptography/mlkem"
import "seekia/internal/cryptography/nacl"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"

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

import "slices"
import "errors"


//Outputs:
//	-[]byte: Message bytes
//	-[26]byte: Message Hash
//	-error
func CreateChatMessage(
		networkType byte,
		senderIdentityHash [16]byte,
		senderIdentityPublicKey [32]byte,
		senderIdentityPrivateKey [64]byte,
		messageCreationTime int64,
		senderCurrentSecretInboxSeed [22]byte,
		senderNextSecretInboxSeed [22]byte,
		senderDeviceIdentifier [11]byte,
		senderLatestChatKeysUpdateTime int64,
		communication string,
		recipientIdentityHash [16]byte,
		recipientInbox [10]byte,
		recipientNaclKey [32]byte,
		recipientMlkemKey [1568]byte,
		recipientCsidhKey [64]byte,
		doubleSealedKeysSealerKey [32]byte)([]byte, [26]byte, error){

	// We create the noncipheredSection, which contains the message inbox, message version, network type, cipherKeyHash, and the doubleSealedKeys

	keyPiece1, err := helpers.GetNewRandom32ByteArray()
	if (err != nil) { return nil, [26]byte{}, err }

	naclEncryptedKeyPiece1, err := nacl.EncryptKeyWithNacl(recipientNaclKey, keyPiece1)
	if (err != nil) { return nil, [26]byte{}, err }

	keyPiece2, mlkemEncryptedKeyPiece2, err := mlkem.EncryptNewRandomKeyWithMlkem(recipientMlkemKey)
	if (err != nil) { return nil, [26]byte{}, err }

	senderCsidhPublicKey, senderCsidhPrivateKey, err := csidh.GetNewRandomPublicPrivateCsidhKeys()
	if (err != nil) { return nil, [26]byte{}, err }

	keyPiece3, err := csidh.CalculateCsidhSharedKey(recipientCsidhKey, senderCsidhPrivateKey)
	if (err != nil) { return nil, [26]byte{}, err }

	basaldataEncryptionKeyDerivationHashInput := slices.Concat(keyPiece1[:], keyPiece2[:], keyPiece3[:])

	basaldataEncryptionKey, err := blake3.Get32ByteBlake3Hash(basaldataEncryptionKeyDerivationHashInput)
	if (err != nil) { return nil, [26]byte{}, err }

	innerSealedKeyPiecesList := slices.Concat(naclEncryptedKeyPiece1[:], mlkemEncryptedKeyPiece2[:], senderCsidhPublicKey[:])

	doubleSealedKeysNonce, err := helpers.GetNewRandom24ByteArray()
	if (err != nil) { return nil, [26]byte{}, err }

	doubleSealedKeys, err := chaPolyShrink.EncryptChaPolyShrink(innerSealedKeyPiecesList, doubleSealedKeysSealerKey, doubleSealedKeysNonce, false, 0, false, [32]byte{})
	if (err != nil) { return nil, [26]byte{}, err }

	// We create the messageCipherKey and messageCipherKeyHash

	messageCipherKey, err := blake3.Get32ByteBlake3Hash(basaldataEncryptionKey[:])
	if (err != nil) { return nil, [26]byte{}, err }

	cipherKeyHash, err := blake3.GetBlake3HashAsBytes(25, messageCipherKey[:])
	if (err != nil) { return nil, [26]byte{}, err }

	// We create the DoubleSealedKeys to store the encryption keys for the cipheredMessage and the basaldata

	messageVersion := appValues.GetMessageVersion()

	cipheredMessageNonce, err := helpers.GetNewRandom24ByteArray()
	if (err != nil) { return nil, [26]byte{}, err }

	messageVersionEncoded, err := encoding.EncodeMessagePackBytes(messageVersion)
	if (err != nil) { return nil, [26]byte{}, err }

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

	recipientInboxEncoded, err := encoding.EncodeMessagePackBytes(recipientInbox)
	if (err != nil) { return nil, [26]byte{}, err }

	doubleSealedKeysNonceEncoded, err := encoding.EncodeMessagePackBytes(doubleSealedKeysNonce)
	if (err != nil) { return nil, [26]byte{}, err }

	doubleSealedKeysEncoded, err := encoding.EncodeMessagePackBytes(doubleSealedKeys)
	if (err != nil) { return nil, [26]byte{}, err }

	cipherKeyHashEncoded, err := encoding.EncodeMessagePackBytes(cipherKeyHash)
	if (err != nil) { return nil, [26]byte{}, err }

	cipheredMessageNonceEncoded, err := encoding.EncodeMessagePackBytes(cipheredMessageNonce)
	if (err != nil) { return nil, [26]byte{}, err }

	noncipheredSectionSlice := []messagepack.RawMessage{messageVersionEncoded, networkTypeEncoded, recipientInboxEncoded, doubleSealedKeysNonceEncoded, doubleSealedKeysEncoded, cipherKeyHashEncoded, cipheredMessageNonceEncoded}

	noncipheredSectionEncoded, err := encoding.EncodeMessagePackBytes(noncipheredSectionSlice)
	if (err != nil) { return nil, [26]byte{}, err }

	// The purpose of the noncipheredSectionHash is to prevent someone from tampering with the message and replaying the ciphered portion
	// By using the noncipheredSectionHash as the additionalData, the noncipheredSection cannot be tampered with

	noncipheredSectionHash, err := blake3.Get32ByteBlake3Hash(noncipheredSectionEncoded)
	if (err != nil) { return nil, [26]byte{}, err }

	// We create the inner message

	timeIsValid := helpers.VerifyCreationTime(messageCreationTime)
	if (timeIsValid == false){
		return nil, [26]byte{}, errors.New("CreateChatMessage called with invalid messageCreationTime.")
	}
	timeIsValid = helpers.VerifyCreationTime(senderLatestChatKeysUpdateTime)
	if (timeIsValid == false){
		return nil, [26]byte{}, errors.New("CreateChatMessage called with invalid senderLatestChatKeysUpdateTime")
	}

	recipientIdentityType, err := identity.GetIdentityTypeFromIdentityHash(recipientIdentityHash)
	if (err != nil){
		recipientIdentityHashHex := encoding.EncodeBytesToHexString(recipientIdentityHash[:])
		return nil, [26]byte{}, errors.New("CreateChatMessage called with invalid recipientIdentityHash: " + recipientIdentityHashHex)
	}

	senderIdentityHashExpected, err := identity.ConvertIdentityKeyToIdentityHash(senderIdentityPublicKey, recipientIdentityType)
	if (err != nil) {
		senderIdentityPublicKeyHex := encoding.EncodeBytesToHexString(senderIdentityPublicKey[:])
		return nil, [26]byte{}, errors.New("CreateChatMessage called with invalid senderIdentityPublicKey: " + senderIdentityPublicKeyHex)
	}

	if (senderIdentityHash != senderIdentityHashExpected){
		senderIdentityHashHex := encoding.EncodeBytesToHexString(senderIdentityHash[:])
		return nil, [26]byte{}, errors.New("CreateChatMessage called with invalid senderIdentityHash: " + senderIdentityHashHex)
	}

	recipientIdentityHashEncoded, err := encoding.EncodeMessagePackBytes(recipientIdentityHash)
	if (err != nil) { return nil, [26]byte{}, err }

	creationTimeEncoded, err := encoding.EncodeMessagePackBytes(messageCreationTime)
	if (err != nil) { return nil, [26]byte{}, err }

	senderCurrentSecretInboxSeedEncoded, err := encoding.EncodeMessagePackBytes(senderCurrentSecretInboxSeed)
	if (err != nil) { return nil, [26]byte{}, err }

	senderNextSecretInboxSeedEncoded, err := encoding.EncodeMessagePackBytes(senderNextSecretInboxSeed)
	if (err != nil) { return nil, [26]byte{}, err }

	senderDeviceIdentifierEncoded, err := encoding.EncodeMessagePackBytes(senderDeviceIdentifier)
	if (err != nil) { return nil, [26]byte{}, err }

	senderLatestChatKeysUpdateTimeEncoded, err := encoding.EncodeMessagePackBytes(senderLatestChatKeysUpdateTime)
	if (err != nil) { return nil, [26]byte{}, err }

	messageBasaldataSlice := []messagepack.RawMessage{recipientIdentityHashEncoded, creationTimeEncoded, senderCurrentSecretInboxSeedEncoded, senderNextSecretInboxSeedEncoded, senderDeviceIdentifierEncoded, senderLatestChatKeysUpdateTimeEncoded}

	messageBasaldataEncoded, err := encoding.EncodeMessagePackBytes(messageBasaldataSlice)
	if (err != nil) { return nil, [26]byte{}, err }

	basaldataEncryptionNonce, err := helpers.GetNewRandom24ByteArray()
	if (err != nil) { return nil, [26]byte{}, err }

	encryptedBasaldata, err := chaPolyShrink.EncryptChaPolyShrink(messageBasaldataEncoded, basaldataEncryptionKey, basaldataEncryptionNonce, false, 0, false, [32]byte{})
	if (err != nil) { return nil, [26]byte{}, err }

	communicationIsAllowed := allowedText.VerifyStringIsAllowed(communication)
	if (communicationIsAllowed == false){
		return nil, [26]byte{}, errors.New("CreateChatMessage called with communication containing unallowed characters.")
	}

	//TODO: Add length restriction to communication

	senderIdentityKeyEncoded, err := encoding.EncodeMessagePackBytes(senderIdentityPublicKey)
	if (err != nil) { return nil, [26]byte{}, err }

	senderIdentityTypeEncoded, err := encoding.EncodeMessagePackBytes(recipientIdentityType)
	if (err != nil) { return nil, [26]byte{}, err }

	basaldataEncryptionNonceEncoded, err := encoding.EncodeMessagePackBytes(basaldataEncryptionNonce)
	if (err != nil) { return nil, [26]byte{}, err }

	encryptedBasaldataEncoded, err := encoding.EncodeMessagePackBytes(encryptedBasaldata)
	if (err != nil) { return nil, [26]byte{}, err }

	communicationEncoded, err := encoding.EncodeMessagePackBytes(communication)
	if (err != nil) { return nil, [26]byte{}, err }

	innerMessageContentSlice := []messagepack.RawMessage{senderIdentityKeyEncoded, senderIdentityTypeEncoded, basaldataEncryptionNonceEncoded, encryptedBasaldataEncoded, communicationEncoded}

	innerMessageContentEncoded, err := encoding.EncodeMessagePackBytes(innerMessageContentSlice)
	if (err != nil) { return nil, [26]byte{}, err }

	innerMessageContentHash, err := blake3.Get32ByteBlake3Hash(innerMessageContentEncoded)
	if (err != nil) { return nil, [26]byte{}, err }

	innerMessageSignature := edwardsKeys.CreateSignature(senderIdentityPrivateKey, innerMessageContentHash)

	signatureEncoded, err := encoding.EncodeMessagePackBytes(innerMessageSignature)
	if (err != nil) { return nil, [26]byte{}, err }

	innerMessageSlice := []messagepack.RawMessage{signatureEncoded, innerMessageContentEncoded}

	innerMessageBytes, err := encoding.EncodeMessagePackBytes(innerMessageSlice)
	if (err != nil) { return nil, [26]byte{}, err }

	cipheredMessage, err := chaPolyShrink.EncryptChaPolyShrink(innerMessageBytes, messageCipherKey, cipheredMessageNonce, true, 1000, true, noncipheredSectionHash)
	if (err != nil) { return nil, [26]byte{}, err }

	cipheredMessageEncoded, err := encoding.EncodeMessagePackBytes(cipheredMessage)
	if (err != nil) { return nil, [26]byte{}, err }

	// We encode the outer message

	finalMessageSlice := []messagepack.RawMessage{noncipheredSectionEncoded, cipheredMessageEncoded}

	finalMessageBytes, err := encoding.EncodeMessagePackBytes(finalMessageSlice)
	if (err != nil) { return nil, [26]byte{}, err }

	messageHash, err := blake3.GetBlake3HashAsBytes(26, finalMessageBytes)
	if (err != nil) { return nil, [26]byte{}, err }

	messageHashArray := [26]byte(messageHash)

	return finalMessageBytes, messageHashArray, nil
}


