
// readReports provides functions to read reports.

package readReports

import "seekia/internal/allowedText"
import "seekia/internal/cryptography/blake3"
import "seekia/internal/encoding"
import "seekia/internal/helpers"

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

import "errors"

func VerifyReport(inputReport []byte)(bool, error){

	ableToRead, _, _, _, _, _, _, err := ReadReport(true, inputReport)
	if (err != nil){
		return false, err
	}
	if (ableToRead == false){
		return false, nil
	}

	return true, nil
}

// This is used to verify report hashes
func VerifyReportHash(inputHash [30]byte, reportTypeProvided bool, expectedReportType string)(bool, error){

	if (reportTypeProvided == true){
		if (expectedReportType != "Identity" && expectedReportType != "Profile" && expectedReportType != "Attribute" && expectedReportType != "Message"){
			return false, errors.New("VerifyReportHash called with invalid expectedReportType: " + expectedReportType)
		}
	}

	reportType, err := GetReportTypeFromReportHash(inputHash)
	if (err != nil){
		return false, nil
	}
	if (reportTypeProvided == true){
		if (reportType != expectedReportType){
			return false, nil
		}
	}

	return true, nil
}


func GetReportTypeFromReportHash(inputHash [30]byte)(string, error){

	metadataByte := inputHash[29]

	if (metadataByte == 1){

		return "Identity", nil
	}
	if (metadataByte == 2){

		return "Profile", nil
	}
	if (metadataByte == 3){

		return "Attribute", nil
	}
	if (metadataByte == 4){
	
		return "Message", nil
	}

	reportHashHex := encoding.EncodeBytesToHexString(inputHash[:])

	return "", errors.New("GetReportTypeFromReportHash called with invalid reportHash: " + reportHashHex)
}


// This function computes the report hash and returns it
//Outputs:
//	-bool: Able to read
//	-[30]byte: Report Hash
//	-int: Report Version
//	-byte: Network Type (1 == Mainnet, 2 == Testnet1)
//	-int64: Creation Time (alleged, could be fake, like all creation times)
//	-string: Report Type
//	-[]byte: Reported Hash
//	-map[string]string: Report map
//	-error
func ReadReportAndHash(verifyReport bool, inputReport []byte)(bool, [30]byte, int, byte, int64, string, []byte, map[string]string, error){

	ableToRead, reportVersion, reportNetworkType, reportCreationTime, reportType, reportedHash, reportMap, err := ReadReport(verifyReport, inputReport)
	if (err != nil){ return false, [30]byte{}, 0, 0, 0, "", nil, nil, err }
	if (ableToRead == false){
		return false, [30]byte{}, 0, 0, 0, "", nil, nil, nil
	}

	reportHashWithoutMetadataByte, err := blake3.GetBlake3HashAsBytes(29, inputReport)
	if (err != nil){ return false, [30]byte{}, 0, 0, 0, "", nil, nil, err }

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

		if (reportType == "Identity"){
			return 1, nil
		}
		if (reportType == "Profile"){
			return 2, nil
		}
		if (reportType == "Attribute"){
			return 3, nil
		}
		if (reportType == "Message"){
			return 4, nil
		}

		return 0, errors.New("ReadReport returning invalid reportType: " + reportType)
	}

	reportHashMetadataByte, err := getReportHashMetadataByte()
	if (err != nil){ return false, [30]byte{}, 0, 0, 0, "", nil, nil, err }

	reportHashSlice := append(reportHashWithoutMetadataByte, reportHashMetadataByte)

	reportHash := [30]byte(reportHashSlice)

	return true, reportHash, reportVersion, reportNetworkType, reportCreationTime, reportType, reportedHash, reportMap, nil
}

// This function does not compute the report hash, thus it is faster
//Outputs:
//	-bool: Able to read
//	-int: Report Version
//	-byte: Network Type (1 == Mainnet, 2 == Testnet1)
//	-int64: Creation Time (alleged, could be fake, like all creation times)
//	-string: Report Type
//	-[]byte: Reported Hash
//	-map[string]string: Report map
//	-error
func ReadReport(verifyReport bool, inputReport []byte)(bool, int, byte, int64, string, []byte, map[string]string, error){

	reportContentMap := make(map[int]messagepack.RawMessage)

	err := messagepack.Unmarshal(inputReport, &reportContentMap)
	if (err != nil) {
		// Invalid Report: Invalid Messagepack
		return false, 0, 0, 0, "", nil, nil, nil
	}

	reportVersionMessagepack, exists := reportContentMap[1]
	if (exists == false){
		// Invalid Report: Missing ReportVersion
		return false, 0, 0, 0, "", nil, nil, nil
	}

	reportVersion, err := encoding.DecodeRawMessagePackToInt(reportVersionMessagepack)
	if (err != nil) {
		// Invalid Report: Invalid report version
		return false, 0, 0, 0, "", nil, nil, nil
	}

	if (reportVersion != 1){
		// We cannot read this report. It was created by a newer version of Seekia.
		return false, 0, 0, 0, "", nil, nil, nil
	}

	networkTypeMessagepack, exists := reportContentMap[2]
	if (exists == false){
		// Invalid Report: Missing NetworkType
		return false, 0, 0, 0, "", nil, nil, nil
	}

	reportNetworkType, err := encoding.DecodeRawMessagePackToByte(networkTypeMessagepack)
	if (err != nil) {
		// Invalid Report: Invalid NetworkType
		return false, 0, 0, 0, "", nil, nil, nil
	}

	isValid := helpers.VerifyNetworkType(reportNetworkType)
	if (isValid == false){
		// Invalid Report: Invalid NetworkType
		return false, 0, 0, 0, "", nil, nil, nil
	}

	creationTimeMessagepack, exists := reportContentMap[3]
	if (exists == false){
		// Invalid Report: Missing CreationTime
		return false, 0, 0, 0, "", nil, nil, nil
	}

	creationTime, err := encoding.DecodeRawMessagePackToInt64(creationTimeMessagepack)
	if (err != nil) {
		// Invalid Report: Invalid CreationTime
		return false, 0, 0, 0, "", nil, nil, nil
	}

	reportedHashMessagepack, exists := reportContentMap[4]
	if (exists == false){
		// Invalid Report: Missing ReportedHash
		return false, 0, 0, 0, "", nil, nil, nil
	}

	reportedHash, err := encoding.DecodeRawMessagePackToBytes(reportedHashMessagepack)
	if (err != nil) {
		// Invalid Report: Invalid ReportedHash
		return false, 0, 0, 0, "", nil, nil, nil
	}

	reportType, err := helpers.GetReportedTypeFromReportedHash(reportedHash)
	if (err != nil){
		// Invalid Report: Invalid ReportedHash
		return false, 0, 0, 0, "", nil, nil, nil
	}

	// These variables are only included in some reports
	var reason string
	var messageCipherKeyBytes []byte

	reasonMessagepack, exists := reportContentMap[5]
	if (exists == true){
		err = messagepack.Unmarshal(reasonMessagepack, &reason)
		if (err != nil) {
			// Invalid Report: Invalid Reason
			return false, 0, 0, 0, "", nil, nil, nil
		}
	}

	if (reportType == "Message"){

		messageCipherKeyMessagepack, exists := reportContentMap[6]
		if (exists == false){
			// Invalid Report: Missing MessageCipherKey
			return false, 0, 0, 0, "", nil, nil, nil
		}

		err = messagepack.Unmarshal(messageCipherKeyMessagepack, &messageCipherKeyBytes)
		if (err != nil) {
			// Invalid Report: Invalid Reason
			return false, 0, 0, 0, "", nil, nil, nil
		}
	}

	if (verifyReport == true){

		isValid := helpers.VerifyCreationTime(creationTime)
		if (isValid == false){
			// Invalid report: Invalid creation time
			return false, 0, 0, 0, "", nil, nil, nil
		}

		if (reportType == "Message"){

			if (len(messageCipherKeyBytes) != 32){
				// Invalid Report: Report contains invalid messageCipherKey
				return false, 0, 0, 0, "", nil, nil, nil
			}
		}

		if (reason != "") {

			if (len(reason) > 200) {
				// Invalid report: Report contains invalid reason
				return false, 0, 0, 0, "", nil, nil, nil
			}

			isAllowed := allowedText.VerifyStringIsAllowed(reason)
			if (isAllowed == false){
				// Invalid report: Report contains invalid reason
				return false, 0, 0, 0, "", nil, nil, nil
			}
		}
	}

	reportVersionString := helpers.ConvertIntToString(reportVersion)

	reportNetworkTypeString := helpers.ConvertByteToString(reportNetworkType)

	creationTimeString := helpers.ConvertInt64ToString(creationTime)

	reportedHashString, err := helpers.EncodeReportedHashBytesToString(reportedHash)
	if (err != nil) { return false, 0, 0, 0, "", nil, nil, err }

	reportMap := map[string]string{
		"ReportVersion": reportVersionString,
		"NetworkType": reportNetworkTypeString,
		"CreationTime": creationTimeString,
		"ReportedHash": reportedHashString,
	}

	if (reportType == "Message"){
		messageCipherKey := encoding.EncodeBytesToHexString(messageCipherKeyBytes)

		reportMap["MessageCipherKey"] = messageCipherKey
	}

	if (reason != ""){
		reportMap["Reason"] = reason
	}

	return true, 1, reportNetworkType, creationTime, reportType, reportedHash, reportMap, nil
}



