
// ethereumAddress provides functions to derive Ethereum addresses for identity hashes and memo hashes

package ethereumAddress

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

import "golang.org/x/crypto/sha3"

import "crypto/rand"
import "encoding/hex"
import "slices"
import "errors"


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

	isValid, err := identity.VerifyIdentityHash(identityHash, true, "Moderator")
	if (err != nil) { return "", err }
	if (isValid == false){
		identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
		return "", errors.New("GetIdentityScoreEthereumAddressFromIdentityHash called with invalid identity hash: " + identityHashHex)
	}

	hashInputSuffix, err := encoding.DecodeBase32StringToBytes("ethereum")
	if (err != nil) { return "", err }

	hashInput := slices.Concat(identityHash[:], hashInputSuffix)

	pseudoRandomBytes, err := blake3.GetBlake3HashAsBytes(20, hashInput)
	if (err != nil) { return "", err }

	resultAddress, err := getEthereumAddressFromBytes(pseudoRandomBytes)
	if (err != nil) { return "", err }

	return resultAddress, nil
}


func GetMemoEthereumAddressFromMemoHash(memoHash [32]byte)(string, error){

	hashInputSuffix, err := encoding.DecodeBase32StringToBytes("memoethereumaddr")
	if (err != nil) { return "", err }

	hashInput := slices.Concat(memoHash[:], hashInputSuffix)

	pseudoRandomBytes, err := blake3.GetBlake3HashAsBytes(20, hashInput)
	if (err != nil) { return "", err }

	resultAddress, err := getEthereumAddressFromBytes(pseudoRandomBytes)
	if (err != nil) { return "", err }

	return resultAddress, nil
}

func GetPaymentProofTreeRootEthereumAddress(treeRootNode [32]byte)(string, error){

	hashInputSuffix, err := encoding.DecodeBase32StringToBytes("paymentprooftreerootetheraddress")
	if (err != nil) { return "", err }

	hashInput := slices.Concat(treeRootNode[:], hashInputSuffix)

	pseudoRandomBytes, err := blake3.GetBlake3HashAsBytes(20, hashInput)
	if (err != nil) { return "", err }

	resultAddress, err := getEthereumAddressFromBytes(pseudoRandomBytes)
	if (err != nil) { return "", err }

	return resultAddress, nil
}


func GetNewRandomEthereumAddress()(string, error){

	randomBytes := make([]byte, 20)
	_, err := rand.Read(randomBytes[:])
	if (err != nil) { return "", err }

	address, err := getEthereumAddressFromBytes(randomBytes)
	if (err != nil) { return "", err }

	return address, nil
}

// Below code is adopted from go-ethereum
// github.com/ethereum/go-ethereum/common/types.go

func getEthereumAddressFromBytes(inputBytes []byte) (string, error){

	if (len(inputBytes) != 20) {
		return "", errors.New("getEthereumAddressFromBytes called with invalid inputBytes: Invalid length.")
	}

	var addressArray [42]byte
	copy(addressArray[:2], "0x")

	hex.Encode(addressArray[2:], inputBytes)

	addressLowercase := string(addressArray[:])

	addressString, err := convertLowercaseEthereumAddressToChecksumAddress(addressLowercase)
	if (err != nil){ return "", err }

	return addressString, nil
}

func VerifyEthereumAddress(input string)bool{

	if (len(input) != 42){
		return false
	}
	if (input[0] != '0'){
		return false
	}
	if (input[1] != 'x' && input[1] != 'X'){		
		return false
	}

	addressWithoutPrefix := input[2:]

	addressWithoutPrefixCharacterBytes := []byte(addressWithoutPrefix)

	// We verify all characters are hexadecimal
	for _, char := range addressWithoutPrefixCharacterBytes{

		if ('0' <= char && char <= '9'){
			continue
		}
		if ('a' <= char && char <= 'f'){
			continue
		}
		if ('A' <= char && char <= 'F'){
			continue
		}
		return false
	}

	addressWithoutPrefixBytes, err := hex.DecodeString(addressWithoutPrefix)
	if (err != nil){
		return false
	}
	
	if (len(addressWithoutPrefixBytes) != 20){
		return false
	}

	// Now we compute checksum and compare with input

	var expectedAddressArray [42]byte
	copy(expectedAddressArray[:2], "0x")
	hex.Encode(expectedAddressArray[2:], addressWithoutPrefixBytes)

	expectedAddressLowercase := string(expectedAddressArray[:])

	expectedAddressString, _ := convertLowercaseEthereumAddressToChecksumAddress(expectedAddressLowercase)

	if (input != expectedAddressString){
		return false
	}

	return true
}

// This function performs the checksum
// An ethereum address checksum is performed by changing some characters to uppercase
// Input:
//	-[]byte: A hex encoded ethereum address with all lowercase characters
// Output:
//	-string: Input address with checksum performed
//	-error
func convertLowercaseEthereumAddressToChecksumAddress(inputAddress string)(string, error){

	inputAddressBytes := []byte(inputAddress)

	if (len(inputAddressBytes) != 42){
		return "", errors.New("convertLowercaseEthereumAddressToChecksumAddress called with invalid address.")
	}

	sha := sha3.NewLegacyKeccak256()
	sha.Write(inputAddressBytes[2:])
	hash := sha.Sum(nil)

	for i := 2; i < len(inputAddressBytes); i++ {

		hashByte := hash[(i-2)/2]
		if (i%2 == 0) {
			hashByte = hashByte >> 4
		} else {
			hashByte &= 0xf
		}

		if (inputAddressBytes[i] > '9' && hashByte > 7) {
			inputAddressBytes[i] -= 32
		}
	}

	addressString := string(inputAddressBytes)

	return addressString, nil
}


