
// peerSecretInboxes provides functions to manage secret inboxes of users that we are chatting with

package peerSecretInboxes

// Secret inboxes are inboxes that users send to us in messages
// They are used to obscure the identity of the recipient, unlike public inboxes.
// Messages should only be sent to them during their epoch.
// After a peer's secret inbox epoch passes, the inbox is expired, and can be deleted.
// This package keeps track of the peer secret inboxes we should be sending our messages to

import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/messaging/inbox"
import "seekia/internal/messaging/peerDevices"
import "seekia/internal/messaging/secretInboxEpoch"
import "seekia/internal/myDatastores/myMapList"

import "errors"
import "sync"
import "time"

// This will be locked whenever we update the map list
var updatingPeerSecretInboxesMapListMutex sync.Mutex

var peerSecretInboxesMapListDatastore *myMapList.MyMapList

// This function must be called whenever an app user signs in
func InitializePeerSecretInboxesDatastore()error{

	updatingPeerSecretInboxesMapListMutex.Lock()
	defer updatingPeerSecretInboxesMapListMutex.Unlock()

	newPeerSecretInboxesMapListDatastore, err := myMapList.CreateNewMapList("PeerSecretInboxes")
	if (err != nil) { return err }

	peerSecretInboxesMapListDatastore = newPeerSecretInboxesMapListDatastore

	return nil
}

//Outputs:
//	-bool: Parameters exist
//	-error
func AddPeerConversationSecretInboxSeeds(myIdentityHash [16]byte, peerIdentityHash [16]byte, networkType byte, messageCreationTime int64, peerCurrentSecretInboxSeed [22]byte, peerNextSecretInboxSeed [22]byte)(bool, error){

	myIdentityHashString, myIdentityType, err := identity.EncodeIdentityHashBytesToString(myIdentityHash)
	if (err != nil){
		myIdentityHashHex := encoding.EncodeBytesToHexString(myIdentityHash[:])
		return false, errors.New("AddPeerConversationSecretInboxSeeds called with invalid myIdentityHash: " + myIdentityHashHex)
	}
	peerIdentityHashString, peerIdentityType, err := identity.EncodeIdentityHashBytesToString(peerIdentityHash)
	if (err != nil) {
		peerIdentityHashHex := encoding.EncodeBytesToHexString(peerIdentityHash[:])
		return false, errors.New("AddPeerConversationSecretInboxSeeds called with invalid peerIdentityHash: " + peerIdentityHashHex)
	}

	if (myIdentityType != peerIdentityType){
		return false, errors.New("AddPeerConversationSecretInboxSeeds called with mismatched my/peer identityTypes.")
	}

	networkTypeIsValid := helpers.VerifyNetworkType(networkType)
	if (networkTypeIsValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return false, errors.New("AddPeerConversationSecretInboxSeeds called with invalid networkType: " + networkTypeString)
	}

	parametersExist, currentSecretInboxStartTime, currentSecretInboxEndTime, nextSecretInboxStartTime, nextSecretInboxEndTime, err := secretInboxEpoch.GetSecretInboxEpochStartAndEndTimes(networkType, messageCreationTime)
	if (err != nil) { return false, err }
	if (parametersExist == false){
		return false, nil
	}

	updatingPeerSecretInboxesMapListMutex.Lock()
	defer updatingPeerSecretInboxesMapListMutex.Unlock()

	peerSecretInboxesMapList, err := peerSecretInboxesMapListDatastore.GetMapList()
	if (err != nil) { return false, err }

	addSecretInboxToPeerInboxesMapList := func(secretInboxSeed [22]byte, inboxStartTime int64, inboxEndTime int64)error{

		secretInboxSeedHex := encoding.EncodeBytesToHexString(secretInboxSeed[:])

		for _, inboxMap := range peerSecretInboxesMapList{

			conversationMyIdentityHash, exists := inboxMap["MyIdentityHash"]
			if (exists == false){
				return errors.New("Malformed peerSecretInboxesMapList: Item missing MyIdentityHash")
			}
		
			if (conversationMyIdentityHash != myIdentityHashString){
				continue
			}

			conversationTheirIdentityHash, exists := inboxMap["TheirIdentityHash"]
			if (exists == false) {
				return errors.New("Malformed peerSecretInboxesMapList: Item missing TheirIdentityHash")
			}
			
			if (conversationTheirIdentityHash != peerIdentityHashString){
				continue
			}

			conversationNetworkType, exists := inboxMap["NetworkType"]
			if (exists == false){
				return errors.New("Malformed peerSecretInboxesMapList: Item missing NetworkType")
			}

			conversationNetworkTypeByte, err := helpers.ConvertNetworkTypeStringToByte(conversationNetworkType)
			if (err != nil){
				return errors.New("Malformed peerSecretInboxesMapList: Item contains invalid NetworkType: " + conversationNetworkType)
			}
			if (conversationNetworkTypeByte != networkType){
				continue
			}

			currentSecretInboxSeed, exists := inboxMap["SecretInboxSeed"]
			if (exists == false){
				return errors.New("Malformed peerSecretInboxesMapList: Item missing SecretInboxSeed")
			}

			if (currentSecretInboxSeed != secretInboxSeedHex){
				continue
			}

			// An entry already exists for this inbox
			// We will update it if needed

			mostRecentSentTime, exists := inboxMap["MostRecentSentTime"]
			if (exists == false){
				return errors.New("Malformed peerSecretInboxesMapList: Item missing MostRecentCreationTime")		
			}
			
			mostRecentSentTimeInt64, err := helpers.ConvertStringToInt64(mostRecentSentTime)
			if (err != nil){
				return errors.New("Malformed peerSecretInboxesMapList: Item contains invalid MostRecentSentTime: " + mostRecentSentTime)
			}

			if (mostRecentSentTimeInt64 < messageCreationTime){
				messageCreationTimeString := helpers.ConvertInt64ToString(messageCreationTime)
				inboxMap["MostRecentSentTime"] = messageCreationTimeString
			}

			existingInboxStartTimeString, exists := inboxMap["InboxStartTime"]
			if (exists == false){
				return errors.New("Malformed peerSecretInboxesMapList: Item missing InboxStartTime")
			}
			existingInboxStartTime, err := helpers.ConvertStringToInt64(existingInboxStartTimeString)
			if (err != nil){
				return errors.New("Malformed peerSecretInboxesMapList: Item contains invalid InboxStartTime: " + existingInboxStartTimeString)
			}

			existingInboxEndTimeString, exists := inboxMap["InboxEndTime"]
			if (exists == false){
				return errors.New("Malformed peerSecretInboxesMapList: Item missing InboxEndTime")
			}
			existingInboxEndTime, err := helpers.ConvertStringToInt64(existingInboxEndTimeString)
			if (err != nil){
				return errors.New("Malformed peerSecretInboxesMapList: Item contains invalid InboxEndTime: " + existingInboxEndTimeString)
			}

			if (existingInboxStartTime <= inboxStartTime && existingInboxEndTime >= inboxEndTime){
				// Existing inbox start/end times are valid
				// Nothing else to change
				return nil
			}

			// Start/end times must be expanded

			newInboxStartTime := min(existingInboxStartTime, inboxStartTime)

			newInboxEndTime := max(existingInboxEndTime, inboxEndTime)

			newInboxStartTimeString := helpers.ConvertInt64ToString(newInboxStartTime)
			newInboxEndTimeString := helpers.ConvertInt64ToString(newInboxEndTime)

			inboxMap["InboxStartTime"] = newInboxStartTimeString
			inboxMap["InboxEndTime"] = newInboxEndTimeString

			return nil
		}

		// This is only reached if the inbox map does not exist.

		networkTypeString := helpers.ConvertByteToString(networkType)
		messageCreationTimeString := helpers.ConvertInt64ToString(messageCreationTime)
		inboxStartTimeString := helpers.ConvertInt64ToString(inboxStartTime)
		inboxEndTimeString := helpers.ConvertInt64ToString(inboxEndTime)

		newInboxMap := map[string]string{
			"MyIdentityHash": myIdentityHashString,
			"TheirIdentityHash": peerIdentityHashString,
			"NetworkType": networkTypeString,
			"SecretInboxSeed": secretInboxSeedHex,
			"MostRecentSentTime": messageCreationTimeString,
			"InboxStartTime": inboxStartTimeString,
			"InboxEndTime": inboxEndTimeString,
		}

		peerSecretInboxesMapList = append(peerSecretInboxesMapList, newInboxMap)

		return nil
	}

	err = addSecretInboxToPeerInboxesMapList(peerCurrentSecretInboxSeed, currentSecretInboxStartTime, currentSecretInboxEndTime)
	if (err != nil) { return false, err }

	err = addSecretInboxToPeerInboxesMapList(peerNextSecretInboxSeed, nextSecretInboxStartTime, nextSecretInboxEndTime)
	if (err != nil) { return false, err }

	err = peerSecretInboxesMapListDatastore.OverwriteMapList(peerSecretInboxesMapList)
	if (err != nil) { return false, err }

	return true, nil
}


//Outputs:
//	-bool: Secret inbox found
//	-[10]byte: Current Secret inbox
//	-[32]byte: Current secret inbox sealer key
//	-error
func GetPeerConversationCurrentSecretInbox(myIdentityHash [16]byte, peerIdentityHash [16]byte, networkType byte)(bool, [10]byte, [32]byte, error){

	myIdentityHashString, myIdentityType, err := identity.EncodeIdentityHashBytesToString(myIdentityHash)
	if (err != nil){
		myIdentityHashHex := encoding.EncodeBytesToHexString(myIdentityHash[:])
		return false, [10]byte{}, [32]byte{}, errors.New("GetPeerConversationCurrentSecretInbox called with invalid myIdentityHash: " + myIdentityHashHex)
	}
	peerIdentityHashString, peerIdentityType, err := identity.EncodeIdentityHashBytesToString(peerIdentityHash)
	if (err != nil) {
		peerIdentityHashHex := encoding.EncodeBytesToHexString(peerIdentityHash[:])
		return false, [10]byte{}, [32]byte{}, errors.New("GetPeerConversationCurrentSecretInbox called with invalid peerIdentityHash: " + peerIdentityHashHex)
	}

	if (myIdentityType != peerIdentityType){
		return false, [10]byte{}, [32]byte{}, errors.New("GetPeerConversationCurrentSecretInbox called with mismatched my/peer identityTypes.")
	}

	networkTypeIsValid := helpers.VerifyNetworkType(networkType)
	if (networkTypeIsValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return false, [10]byte{}, [32]byte{}, errors.New("GetPeerConversationCurrentSecretInbox called with invalid networkType: " + networkTypeString)
	}

	// We find the newest available peer secret inbox seed for this conversation
	// We first retrieve all peer secret inboxes for this conversation that exist on this network

	networkTypeString := helpers.ConvertByteToString(networkType)

	lookupMap := map[string]string{
		"MyIdentityHash": myIdentityHashString,
		"TheirIdentityHash": peerIdentityHashString,
		"NetworkType": networkTypeString,
	}

	anyExist, peerSecretInboxesMapList, err := peerSecretInboxesMapListDatastore.GetMapListItems(lookupMap)
	if (err != nil) { return false, [10]byte{}, [32]byte{}, err }
	if (anyExist == false){
		return false, [10]byte{}, [32]byte{}, nil
	}

	currentTime := time.Now().Unix()

	anyValidInboxFound := false
	var newestSecretInboxSeed [22]byte
	newestSecretInboxMostRecentSentTime := int64(0)

	for _, inboxMap := range peerSecretInboxesMapList{

		inboxStartTimeString, exists := inboxMap["InboxStartTime"]
		if (exists == false){
			return false, [10]byte{}, [32]byte{}, errors.New("Malformed peerSecretInboxesMapList: Item missing InboxStartTime")
		}

		inboxStartTime, err := helpers.ConvertStringToInt64(inboxStartTimeString)
		if (err != nil){
			return false, [10]byte{}, [32]byte{}, errors.New("Malformed peerSecretInboxesMapList: Item contains invalid inboxStartTime: " + inboxStartTimeString)
		}

		inboxEndTimeString, exists := inboxMap["InboxEndTime"]
		if (exists == false){
			return false, [10]byte{}, [32]byte{}, errors.New("Malformed peerSecretInboxesMapList: Item missing InboxEndTime")
		}

		inboxEndTime, err := helpers.ConvertStringToInt64(inboxEndTimeString)
		if (err != nil){
			return false, [10]byte{}, [32]byte{}, errors.New("Malformed peerSecretInboxesMapList: Item contains invalid inboxEndTime: " + inboxEndTimeString)
		}

		if (inboxStartTime > currentTime || inboxEndTime < currentTime){
			// Inbox is not valid right now. Skip it.
			continue
		}

		secretInboxSeedString, exists := inboxMap["SecretInboxSeed"]
		if (exists == false){
			return false, [10]byte{}, [32]byte{}, errors.New("Malformed peerSecretInboxesMapList: Item missing SecretInboxSeed")
		}

		secretInboxSeedBytes, err := encoding.DecodeHexStringToBytes(secretInboxSeedString)
		if (err != nil){
			return false, [10]byte{}, [32]byte{}, errors.New("Malformed peerSecretInboxesMapList: Item contains invalid SecretInboxSeed: Not Hex.")
		}

		if (len(secretInboxSeedBytes) != 22){
			return false, [10]byte{}, [32]byte{}, errors.New("Malformed peerSecretInboxesMapList: Item contains invalid SecretInboxSeed: Invalid length.")
		}

		secretInboxSeed := [22]byte(secretInboxSeedBytes)

		inboxMostRecentSentTimeString, exists := inboxMap["MostRecentSentTime"]
		if (exists == false){
			return false, [10]byte{}, [32]byte{}, errors.New("Malformed peerSecretInboxesMapList: Item missing MostRecentSentTime")
		}

		inboxMostRecentSentTime, err := helpers.ConvertStringToInt64(inboxMostRecentSentTimeString)
		if (err != nil){
			return false, [10]byte{}, [32]byte{}, errors.New("Malformed peerSecretInboxesMapList: Item contains invalid MostRecentSentTime: " + inboxMostRecentSentTimeString)
		}

		if (anyValidInboxFound == false || newestSecretInboxMostRecentSentTime < inboxMostRecentSentTime){

			anyValidInboxFound = true
			newestSecretInboxMostRecentSentTime = inboxMostRecentSentTime
			newestSecretInboxSeed = secretInboxSeed
		}
	}

	if (anyValidInboxFound == false){
		return false, [10]byte{}, [32]byte{}, nil
	}

	// Now we check if their device identifier has changed after we received this secret inbox

	deviceInfoFound, _, newestDeviceFirstSeenTime, err := peerDevices.GetPeerNewestDeviceInfo(peerIdentityHash, networkType)
	if (err != nil) { return false, [10]byte{}, [32]byte{}, err }
	if (deviceInfoFound == true && newestDeviceFirstSeenTime > newestSecretInboxMostRecentSentTime){

		// The peer has changed their device after they created a message containing this secret inbox
		// We assume this inbox was not carried over to the new device, and we say that no secret inbox exists

		return false, [10]byte{}, [32]byte{}, nil
	}

	newestSecretInbox, currentSecretInboxSealerKey, err := inbox.GetSecretInboxAndSealerKeyFromSecretInboxSeed(newestSecretInboxSeed)
	if (err != nil){
		return false, [10]byte{}, [32]byte{}, errors.New("Malformed peerSecretInboxesMapList: Item contains invalid SecretInboxSeed.")
	}

	return true, newestSecretInbox, currentSecretInboxSealerKey, nil
}


