
// identity provides functions for creating and reading identity keys and identity hashes

package identity

// Identity Keys are Edwards Keys used for identity purposes.

import "seekia/internal/encoding"
import "seekia/internal/cryptography/blake3"
import "seekia/internal/cryptography/edwardsKeys"

import "bytes"
import "slices"
import cryptoRand "crypto/rand"
import mathRand "math/rand/v2"
import "errors"


func VerifyIdentityKeyHex(identityKey string)bool{

	isValid := edwardsKeys.VerifyPublicKeyHex(identityKey)
	if (isValid == false){
		return false
	}

	return true
}

func VerifyIdentityHash(identityHash [16]byte, identityTypeProvided bool, identityType string)(bool, error){

	if (identityTypeProvided == true){
		if (identityType != "Mate" && identityType != "Host" && identityType != "Moderator"){
			return false, errors.New("VerifyIdentityHash called with invalid provided identityType: " + identityType)
		}
	}

	userIdentityType, err := GetIdentityTypeFromIdentityHash(identityHash)
	if (err != nil){
		return false, nil
	}

	if (identityTypeProvided == false){
		return true, nil
	}

	if (userIdentityType != identityType){
		return false, nil
	}

	return true, nil
}


func GetIdentityTypeFromIdentityHash(identityHash [16]byte)(string, error){

	identityTypeByte := identityHash[15]

	if (identityTypeByte == 1){
		return "Mate", nil
	}
	if (identityTypeByte == 2){
		return "Host", nil
	}
	if (identityTypeByte == 3){
		return "Moderator", nil
	}

	identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])

	return "", errors.New("GetIdentityTypeFromIdentityHash called with invalid identity hash: " + identityHashHex)
}

//Outputs:
//	-[16]byte: Bytes representation of identity hash
//	-string: Identity type of identity hash
//	-error
func ReadIdentityHashString(identityHash string)([16]byte, string, error){

	if (len(identityHash) != 27){
		return [16]byte{}, "", errors.New("ReadIdentityHashString called with invalid identity hash: Invalid length: " + identityHash)
	}

	identityKeyHashString := identityHash[:24]
	checksumString := identityHash[24:26]
	identityTypeCharacter := string(identityHash[26])

	identityKeyHashBytes, err := encoding.DecodeBase32StringToBytes(identityKeyHashString)
	if (err != nil) {
		return [16]byte{}, "", errors.New("ReadIdentityHashString called with invalid identity hash: Contains invalid characters: " + identityHash + ": " + err.Error())
	}

	if (len(identityKeyHashBytes) != 15) {
		return [16]byte{}, "", errors.New("ReadIdentityHashString called with invalid identity hash: Invalid bytes length: " + identityHash)
	}

	checksumByte, err := encoding.DecodeHexStringToBytes(checksumString)
	if (err != nil){
		return [16]byte{}, "", errors.New("ReadIdentityHashString called with invalid identity hash: Invalid checksum: Not Hex: " + identityHash)
	}

	if (len(checksumByte) != 1){
		return [16]byte{}, "", errors.New("ReadIdentityHashString called with invalid identity hash: Invalid checksum: Invalid Length: " + identityHash)
	}

	expectedChecksumByte, err := blake3.GetBlake3HashAsBytes(1, identityKeyHashBytes)
	if (err != nil) { return [16]byte{}, "", err }

	areEqual := bytes.Equal(checksumByte, expectedChecksumByte)
	if (areEqual == false){
		return [16]byte{}, "", errors.New("ReadIdentityHashString called with invalid identity hash: Invalid checksum: " + identityHash)
	}

	getIdentityTypeNameAndByte := func()(string, byte, error){

		if (identityTypeCharacter == "m"){
			return "Mate", 1, nil
		}
		if (identityTypeCharacter == "h"){
			return "Host", 2, nil
		}
		if (identityTypeCharacter == "r"){
			return "Moderator", 3, nil
		}

		return "", 0, errors.New("ReadIdentityHashString called with invalid identity hash: Invalid identityType character: " + identityTypeCharacter + " from " + identityHash)
	}

	identityType, identityTypeByte, err := getIdentityTypeNameAndByte()
	if (err != nil) { return [16]byte{}, "", err }

	identityHashBytes := append(identityKeyHashBytes, identityTypeByte)

	identityHashBytesArray := [16]byte(identityHashBytes)

	return identityHashBytesArray, identityType, nil
}


//Outputs:
//	-string: Encoded identity hash
//	-string: Identity type
//	-error
func EncodeIdentityHashBytesToString(identityHashBytes [16]byte)(string, string, error){

	identityKeyHashBytes := identityHashBytes[:15]
	identityTypeByte := identityHashBytes[15]

	checksumByte, err := blake3.GetBlake3HashAsBytes(1, identityKeyHashBytes)
	if (err != nil) { return "", "", err }

	identityKeyHashString := encoding.EncodeBytesToBase32String(identityKeyHashBytes)

	checksumByteHex := encoding.EncodeBytesToHexString(checksumByte)

	//Outputs:
	//	-string: Identity type
	//	-string: Identity type character
	//	-error
	getIdentityTypeWithCharacter := func()(string, string, error){

		if (identityTypeByte == 1){
	
			return "Mate", "m", nil
		}
		if (identityTypeByte == 2){

			return "Host", "h", nil
		}
		if (identityTypeByte == 3){

			return "Moderator", "r", nil
		}

		return "", "", errors.New("EncodeIdentityHashBytesToString called with invalid identityHashBytes: Contains invalid identityTypeByte.")
	}

	identityType, identityTypeCharacter, err := getIdentityTypeWithCharacter()
	if (err != nil) { return "", "", err }

	identityHash := identityKeyHashString + checksumByteHex + identityTypeCharacter

	return identityHash, identityType, nil
}

func ConvertIdentityKeyToIdentityHash(identityKey [32]byte, identityType string)([16]byte, error){

	if (identityType != "Mate" && identityType != "Host" && identityType != "Moderator"){
		return [16]byte{}, errors.New("ConvertIdentityKeyToIdentityHash called with invalid identityType: " + identityType)
	}

	identityKeyHashBytes, err := blake3.GetBlake3HashAsBytes(15, identityKey[:])
	if (err != nil) { return [16]byte{}, err }

	getIdentityTypeByte := func()byte{

		if (identityType == "Mate"){
			return 1
		}
		if (identityType == "Host"){
			return 2
		}

		return 3
	}

	identityTypeByte := getIdentityTypeByte()

	identityHashBytes := append(identityKeyHashBytes, identityTypeByte)

	identityHashArray := [16]byte(identityHashBytes)

	return identityHashArray, nil
}


func GetNewRandomIdentityHash(identityTypeProvided bool, identityType string)([16]byte, error){

	identityKeyHashBytes := make([]byte, 15, 16)
	_, err := cryptoRand.Read(identityKeyHashBytes[:])
	if (err != nil){ return [16]byte{}, err }

	getIdentityTypeByte := func()(byte, error){

		if (identityTypeProvided == false){

			randomIndex := mathRand.IntN(3)

			if (randomIndex == 0){
				return 1, nil
			}
			if (randomIndex == 1){
				return 2, nil
			}
			// randomIndex == 2
			return 3, nil
		}

		if (identityType == "Mate"){
			return 1, nil
		}
		if (identityType == "Host"){
			return 2, nil
		}
		if (identityType == "Moderator"){
			return 3, nil
		}

		return 0, errors.New("GetNewRandomIdentityHash called with invalid identityType: " + identityType)
	}

	identityTypeByte, err := getIdentityTypeByte()
	if (err != nil) { return [16]byte{}, err }

	identityHashBytes := append(identityKeyHashBytes, identityTypeByte)

	identityHashArray := [16]byte(identityHashBytes)

	return identityHashArray, nil
}

func GetPublicPrivateIdentityKeysFromSeedPhraseHash(inputSeedPhraseHash [32]byte)([32]byte, [64]byte, error){

	// This is the salt used to derive identity hashes from seed phrase hashes
	// It is the string "identitykeysseed" decoded from base32 to bytes.
	identityKeysSalt := []byte{64, 200, 217, 162, 120, 81, 49, 41, 16, 131}

	hashInput := slices.Concat(inputSeedPhraseHash[:], identityKeysSalt)

	seedBytes, err := blake3.Get32ByteBlake3Hash(hashInput)
	if (err != nil) { return [32]byte{}, [64]byte{}, err }

	publicKeyArray, privateKeyArray := edwardsKeys.GetSeededEdwardsPublicAndPrivateKeys(seedBytes)

	return publicKeyArray, privateKeyArray, nil
}

func GetNewRandomPublicPrivateIdentityKeys()([32]byte, [64]byte, error){

	publicKeyArray, privateKeyArray, err := edwardsKeys.GetNewRandomPublicAndPrivateEdwardsKeys()
	if (err != nil) { return [32]byte{}, [64]byte{}, err }

	return publicKeyArray, privateKeyArray, nil
}

func GetIdentityHashFromSeedPhraseHash(seedPhraseHash [32]byte, identityType string)([16]byte, error){

	publicIdentityKey, _, err := GetPublicPrivateIdentityKeysFromSeedPhraseHash(seedPhraseHash)
	if (err != nil) { return [16]byte{}, err }

	identityHash, err := ConvertIdentityKeyToIdentityHash(publicIdentityKey, identityType)
	if (err != nil) { return [16]byte{}, err }

	return identityHash, nil
}


// We use this function to generate custom identity hashes
// This is faster, because we only have to generate the first 10 bytes of the identity hash, and we don't have to calculate the checksum
func GetIdentityHash16CharacterPrefixFromSeedPhraseHash(seedPhraseHash [32]byte)(string, error){

	publicIdentityKey, _, err := GetPublicPrivateIdentityKeysFromSeedPhraseHash(seedPhraseHash)
	if (err != nil) { return "", err }

	identityKeyHashBytes, err := blake3.GetBlake3HashAsBytes(10, publicIdentityKey[:])
	if (err != nil) { return "", err }

	identityHashPrefix := encoding.EncodeBytesToBase32String(identityKeyHashBytes)

	return identityHashPrefix, nil
}

