// Copyright 2024 The Erigon Authors
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Erigon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.

package accounts

import (
	"fmt"
	"io"
	"math/bits"
	"sync"

	"github.com/holiman/uint256"

	"github.com/erigontech/erigon/common"
	"github.com/erigontech/erigon/common/empty"
	"github.com/erigontech/erigon/execution/rlp"
)

// Account is the Ethereum consensus representation of accounts.
// These objects are stored in the main account trie.
// DESCRIBED: docs/programmers_guide/guide.md#ethereum-state
type Account struct {
	Nonce           uint64
	Balance         uint256.Int
	Root            common.Hash // merkle root of the storage trie
	CodeHash        CodeHash    // hash of the bytecode
	Incarnation     uint64
	PrevIncarnation uint64
}

const (
	MimetypeDataWithValidator = "data/validator"
	MimetypeTypedData         = "data/typed"
	MimetypeClique            = "application/x-clique-header"
	MimetypeBor               = "application/x-bor-header"
	MimetypeTextPlain         = "text/plain"
)

// NewAccount creates a new account w/o code nor storage.
func NewAccount() Account {
	return Account{
		Root:     empty.RootHash,
		CodeHash: EmptyCodeHash,
	}
}

func (a *Account) EncodingLengthForStorage() uint {
	var structLength uint = 1 // 1 byte for fieldset

	if !a.Balance.IsZero() {
		structLength += uint(a.Balance.ByteLen()) + 1
	}

	if a.Nonce > 0 {
		structLength += uint(common.BitLenToByteLen(bits.Len64(a.Nonce))) + 1
	}

	if !a.IsEmptyCodeHash() {
		structLength += 33 // 32-byte array + 1 bytes for length
	}

	if a.Incarnation > 0 {
		structLength += uint(common.BitLenToByteLen(bits.Len64(a.Incarnation))) + 1
	}

	return structLength
}

func (a *Account) EncodingLengthForHashing() uint {
	structLength := rlp.Uint256Len(a.Balance) + rlp.U64Len(a.Nonce)
	structLength += 66 // Two 32-byte arrays + 2 prefixes
	return uint(rlp.ListPrefixLen(structLength) + structLength)
}

func (a *Account) EncodeForStorage(buffer []byte) {
	var fieldSet = 0 // start with first bit set to 0
	var pos = 1
	if a.Nonce > 0 {
		fieldSet = 1
		nonceBytes := common.BitLenToByteLen(bits.Len64(a.Nonce))
		buffer[pos] = byte(nonceBytes)
		var nonce = a.Nonce
		for i := nonceBytes; i > 0; i-- {
			buffer[pos+i] = byte(nonce)
			nonce >>= 8
		}
		pos += nonceBytes + 1
	}

	// Encoding balance
	if !a.Balance.IsZero() {
		fieldSet |= 2
		balanceBytes := a.Balance.ByteLen()
		buffer[pos] = byte(balanceBytes)
		pos++
		a.Balance.WriteToSlice(buffer[pos : pos+balanceBytes])
		pos += balanceBytes
	}

	if a.Incarnation > 0 {
		fieldSet |= 4
		incarnationBytes := common.BitLenToByteLen(bits.Len64(a.Incarnation))
		buffer[pos] = byte(incarnationBytes)
		var incarnation = a.Incarnation
		for i := incarnationBytes; i > 0; i-- {
			buffer[pos+i] = byte(incarnation)
			incarnation >>= 8
		}
		pos += incarnationBytes + 1
	}

	// Encoding CodeHash
	if !a.IsEmptyCodeHash() {
		fieldSet |= 8
		buffer[pos] = 32
		codeHashValue := a.CodeHash.Value()
		copy(buffer[pos+1:], codeHashValue[:])
		//pos += 33
	}

	buffer[0] = byte(fieldSet)
}

// Decodes length and determines whether it corresponds to a structure of a byte array
func decodeLengthForHashing(buffer []byte, pos int) (length int, structure bool, newPos int) {
	switch firstByte := int(buffer[pos]); {
	case firstByte < 128:
		return 0, false, pos
	case firstByte < 184:
		return firstByte - 128, false, pos + 1
	case firstByte < 192:
		// Next byte is the length of the length + 183
		lenEnd := pos + 1 + firstByte - 183
		for i := pos + 1; i < lenEnd; i++ {
			length = (length << 8) + int(buffer[i])
		}
		return length, false, lenEnd
	case firstByte < 248:
		return firstByte - 192, true, pos + 1
	default:
		// Next byte is the length of the length + 247
		lenEnd := pos + 1 + firstByte - 247
		for i := pos + 1; i < lenEnd; i++ {
			length = (length << 8) + int(buffer[i])
		}
		return length, true, lenEnd
	}
}

var rlpEncodingBufPool = sync.Pool{
	New: func() any {
		buf := make([]byte, 0, 128)
		return &buf
	},
}

func (a *Account) EncodeRLP(w io.Writer) error {
	var buf []byte
	l := a.EncodingLengthForHashing()
	if l > 128 {
		buf = make([]byte, l)
	} else {
		bp := rlpEncodingBufPool.Get().(*[]byte)
		defer rlpEncodingBufPool.Put(bp)
		buf = *bp
		buf = buf[:l]
	}

	a.EncodeForHashing(buf)
	_, err := w.Write(buf)
	return err
}

// returns the RLP encoding of the account directly as a []byte
func (a *Account) RLP() []byte {
	accRlp := make([]byte, a.EncodingLengthForHashing())
	a.EncodeForHashing(accRlp)
	return accRlp
}

// TODO(yperbasis): simplify
func (a *Account) EncodeForHashing(buffer []byte) {
	balanceBytes := 0
	if !a.Balance.LtUint64(128) {
		balanceBytes = a.Balance.ByteLen()
	}

	nonceBytes := rlp.U64Len(a.Nonce) - 1

	var structLength = uint(balanceBytes + nonceBytes + 2)
	structLength += 66 // Two 32-byte arrays + 2 prefixes

	var pos int
	if structLength < 56 {
		buffer[0] = byte(192 + structLength)
		pos = 1
	} else {
		lengthBytes := common.BitLenToByteLen(bits.Len(structLength))
		buffer[0] = byte(247 + lengthBytes)

		for i := lengthBytes; i > 0; i-- {
			buffer[i] = byte(structLength)
			structLength >>= 8
		}

		pos = lengthBytes + 1
	}

	// Encoding nonce
	if a.Nonce < 128 && a.Nonce != 0 {
		buffer[pos] = byte(a.Nonce)
	} else {
		buffer[pos] = byte(128 + nonceBytes)
		var nonce = a.Nonce
		for i := nonceBytes; i > 0; i-- {
			buffer[pos+i] = byte(nonce)
			nonce >>= 8
		}
	}
	pos += 1 + nonceBytes

	// Encoding balance
	if a.Balance.LtUint64(128) && !a.Balance.IsZero() {
		buffer[pos] = byte(a.Balance.Uint64())
		pos++
	} else {
		buffer[pos] = byte(128 + balanceBytes)
		pos++
		a.Balance.WriteToSlice(buffer[pos : pos+balanceBytes])
		pos += balanceBytes
	}

	// Encoding Root and CodeHash
	buffer[pos] = 128 + 32
	pos++
	copy(buffer[pos:], a.Root[:])
	pos += 32
	buffer[pos] = 128 + 32
	pos++
	codeHashValue := a.CodeHash.Value()
	copy(buffer[pos:], codeHashValue[:])
	//pos += 32
}

// Copy makes `a` a full, independent (meaning that if the `image` changes in any way, it does not affect `a`) copy of the account `image`.
func (a *Account) Copy(image *Account) {
	a.Nonce = image.Nonce
	a.Balance.Set(&image.Balance)
	copy(a.Root[:], image.Root[:])
	a.CodeHash = image.CodeHash
	a.Incarnation = image.Incarnation
}

func (a *Account) Empty() bool {
	return a == nil || (a.Nonce == 0 && a.Balance.IsZero() && a.CodeHash == EmptyCodeHash)
}

func (a *Account) DecodeForHashing(enc []byte) error {
	length, structure, pos := decodeLengthForHashing(enc, 0)
	if pos+length != len(enc) {
		return fmt.Errorf(
			"malformed RLP for Account(%x): prefixLength(%d) + dataLength(%d) != sliceLength(%d)",
			enc, pos, length, len(enc))
	}
	if !structure {
		return fmt.Errorf(
			"encoding of Account should be RLP struct, got byte array: %x",
			enc,
		)
	}

	a.Nonce = 0
	a.Balance.Clear()
	a.Root = empty.RootHash
	a.CodeHash = EmptyCodeHash
	if length == 0 && structure {
		return nil
	}

	if pos < len(enc) {
		nonceBytes, s, newPos := decodeLengthForHashing(enc, pos)
		if s {
			return fmt.Errorf(
				"encoding of Account.Nonce should be byte array, got RLP struct: %x",
				enc[pos:newPos+nonceBytes],
			)
		}

		if newPos+nonceBytes > len(enc) {
			return fmt.Errorf(
				"malformed RLP for Account.Nonce(%x): prefixLength(%d) + dataLength(%d) >= sliceLength(%d)",
				enc[pos:newPos+nonceBytes],
				newPos-pos, nonceBytes, len(enc)-pos,
			)
		}

		var nonce uint64
		if nonceBytes == 0 && newPos == pos {
			nonce = uint64(enc[newPos])
			pos = newPos + 1
		} else {
			for _, b := range enc[newPos : newPos+nonceBytes] {
				nonce = (nonce << 8) + uint64(b)
			}
			pos = newPos + nonceBytes
		}

		a.Nonce = nonce
	}
	if pos < len(enc) {
		balanceBytes, s, newPos := decodeLengthForHashing(enc, pos)
		if s {
			return fmt.Errorf(
				"encoding of Account.Balance should be byte array, got RLP struct: %x",
				enc[pos:newPos+balanceBytes],
			)
		}

		if newPos+balanceBytes > len(enc) {
			return fmt.Errorf("malformed RLP for Account.Balance(%x): prefixLength(%d) + dataLength(%d) >= sliceLength(%d)",
				enc[pos],
				newPos-pos, balanceBytes, len(enc)-pos,
			)
		}

		if balanceBytes == 0 && newPos == pos {
			a.Balance.SetUint64(uint64(enc[newPos]))
			pos = newPos + 1
		} else {
			a.Balance.SetBytes(enc[newPos : newPos+balanceBytes])
			pos = newPos + balanceBytes
		}
	}
	if pos < len(enc) {
		rootBytes, s, newPos := decodeLengthForHashing(enc, pos)
		if s {
			return fmt.Errorf(
				"encoding of Account.Root should be byte array, got RLP struct: %x",
				enc[pos:newPos+rootBytes],
			)
		}

		if rootBytes != 32 {
			return fmt.Errorf(
				"encoding of Account.Root should have size 32, got %d",
				rootBytes,
			)
		}

		if newPos+rootBytes > len(enc) {
			return fmt.Errorf("malformed RLP for Account.Root(%x): prefixLength(%d) + dataLength(%d) >= sliceLength(%d)",
				enc[pos],
				newPos-pos, rootBytes, len(enc)-pos,
			)
		}

		copy(a.Root[:], enc[newPos:newPos+rootBytes])
		pos = newPos + rootBytes
	}

	if pos < len(enc) {
		codeHashBytes, s, newPos := decodeLengthForHashing(enc, pos)
		if s {
			return fmt.Errorf(
				"encoding of Account.CodeHash should be byte array, got RLP struct: %x",
				enc[pos:newPos+codeHashBytes],
			)
		}

		if codeHashBytes != 32 {
			return fmt.Errorf(
				"encoding of Account.CodeHash should have size 32, got %d",
				codeHashBytes,
			)
		}

		if newPos+codeHashBytes > len(enc) {
			return fmt.Errorf("malformed RLP for Account.CodeHash(%x): prefixLength(%d) + dataLength(%d) >= sliceLength(%d)",
				enc[pos:newPos+codeHashBytes],
				newPos-pos, codeHashBytes, len(enc)-pos,
			)
		}

		var codeHashValue common.Hash
		copy(codeHashValue[:], enc[newPos:newPos+codeHashBytes])
		a.CodeHash = InternCodeHash(codeHashValue)

		pos = newPos + codeHashBytes
	}

	if pos < len(enc) {
		storageSizeBytes, s, newPos := decodeLengthForHashing(enc, pos)
		if s {
			return fmt.Errorf(
				"encoding of Account.StorageSize should be byte array, got RLP struct: %x",
				enc[pos:newPos+storageSizeBytes],
			)
		}

		if newPos+storageSizeBytes > len(enc) {
			return fmt.Errorf(
				"malformed RLP for Account.StorageSize(%x): prefixLength(%d) + dataLength(%d) >= sliceLength(%d)",
				enc[pos:newPos+storageSizeBytes],
				newPos-pos, storageSizeBytes, len(enc)-pos,
			)
		}

		// Commented out because of the ineffectual assignment - uncomment if adding more fields
		var storageSize uint64
		if storageSizeBytes == 0 && newPos == pos {
			storageSize = uint64(enc[newPos])
			pos = newPos + 1
		} else {
			for _, b := range enc[newPos : newPos+storageSizeBytes] {
				storageSize = (storageSize << 8) + uint64(b)
			}
			pos = newPos + storageSizeBytes
		}
		_ = storageSize
	}
	_ = pos

	return nil
}

func (a *Account) Reset() {
	a.Nonce = 0
	a.Incarnation = 0
	a.Balance.Clear()
	a.Root = empty.RootHash
	a.CodeHash = EmptyCodeHash
}

func (a *Account) DecodeForStorage(enc []byte) error {
	a.Reset()

	if len(enc) == 0 {
		return nil
	}

	var fieldSet = enc[0]
	var pos = 1

	if fieldSet&1 > 0 {
		decodeLength := int(enc[pos])

		if len(enc) < pos+decodeLength+1 {
			return fmt.Errorf(
				"malformed CBOR for Account.Nonce: %s, Length %d",
				enc[pos+1:], decodeLength)
		}

		a.Nonce = common.BytesToUint64(enc[pos+1 : pos+decodeLength+1])
		pos += decodeLength + 1
	}

	if fieldSet&2 > 0 {
		decodeLength := int(enc[pos])

		if len(enc) < pos+decodeLength+1 {
			return fmt.Errorf(
				"malformed CBOR for Account.Nonce: %s, Length %d",
				enc[pos+1:], decodeLength)
		}

		a.Balance.SetBytes(enc[pos+1 : pos+decodeLength+1])
		pos += decodeLength + 1
	}

	if fieldSet&4 > 0 {
		decodeLength := int(enc[pos])

		if len(enc) < pos+decodeLength+1 {
			return fmt.Errorf(
				"malformed CBOR for Account.Incarnation: %s, Length %d",
				enc[pos+1:], decodeLength)
		}

		a.Incarnation = common.BytesToUint64(enc[pos+1 : pos+decodeLength+1])
		pos += decodeLength + 1
	}

	if fieldSet&8 > 0 {

		decodeLength := int(enc[pos])

		if decodeLength != 32 {
			return fmt.Errorf("codehash should be 32 bytes long, got %d instead",
				decodeLength)
		}

		if len(enc) < pos+decodeLength+1 {
			return fmt.Errorf(
				"malformed CBOR for Account.CodeHash: %s, Length %d",
				enc[pos+1:], decodeLength)
		}
		var codeHashValue common.Hash
		copy(codeHashValue[:], enc[pos+1:pos+decodeLength+1])
		a.CodeHash = InternCodeHash(codeHashValue)
		pos += decodeLength + 1
	}

	_ = pos

	return nil
}

func DecodeIncarnationFromStorage(enc []byte) (uint64, error) {
	if len(enc) == 0 {
		return 0, nil
	}

	var fieldSet = enc[0]
	var pos = 1

	//looks for the position incarnation is at
	if fieldSet&1 > 0 {
		decodeLength := int(enc[pos])
		if len(enc) < pos+decodeLength+1 {
			return 0, fmt.Errorf(
				"malformed CBOR for Account.Nonce: %s, Length %d",
				enc[pos+1:], decodeLength)
		}
		pos += decodeLength + 1
	}

	if fieldSet&2 > 0 {
		decodeLength := int(enc[pos])
		if len(enc) < pos+decodeLength+1 {
			return 0, fmt.Errorf(
				"malformed CBOR for Account.Nonce: %s, Length %d",
				enc[pos+1:], decodeLength)
		}
		pos += decodeLength + 1
	}

	if fieldSet&4 > 0 {
		decodeLength := int(enc[pos])

		//checks if the ending position is correct if not returns 0
		if len(enc) < pos+decodeLength+1 {
			return 0, fmt.Errorf(
				"malformed CBOR for Account.Incarnation: %s, Length %d",
				enc[pos+1:], decodeLength)
		}

		incarnation := common.BytesToUint64(enc[pos+1 : pos+decodeLength+1])
		return incarnation, nil
	}

	return 0, nil

}

func (a *Account) SelfCopy() *Account {
	newAcc := NewAccount()
	newAcc.Copy(a)
	return &newAcc
}

func (a *Account) DecodeRLP(s *rlp.Stream) error {
	raw, err := s.Raw()
	if err != nil {
		return err
	}

	err = a.DecodeForHashing(raw)
	return err
}

func (a *Account) IsEmptyCodeHash() bool {
	return a.CodeHash.IsEmpty()
}

func (a *Account) IsEmptyRoot() bool {
	return a.Root == empty.RootHash || a.Root == common.Hash{}
}

func (a *Account) GetIncarnation() uint64 {
	return a.Incarnation
}

func (a *Account) SetIncarnation(v uint64) {
	a.Incarnation = v
}

func (a *Account) Equals(acc *Account) bool {
	return a.Nonce == acc.Nonce &&
		a.CodeHash == acc.CodeHash &&
		a.Balance.Cmp(&acc.Balance) == 0 &&
		a.Incarnation == acc.Incarnation
}

func ConvertV3toV2(v []byte) ([]byte, error) {
	var a Account
	if err := DeserialiseV3(&a, v); err != nil {
		return nil, fmt.Errorf("ConvertV3toV2(%x): %w", v, err)
	}
	v = make([]byte, a.EncodingLengthForStorage())
	a.EncodeForStorage(v)
	return v, nil
}

// DeserialiseV3 - method to deserialize accounts in Erigon22 history
func DeserialiseV3(a *Account, enc []byte) error {
	a.Reset()
	//if len(enc) == 0 {
	//	return nil
	//}
	pos := 0
	nonceBytes := int(enc[pos])
	pos++
	if nonceBytes > 0 {
		a.Nonce = common.BytesToUint64(enc[pos : pos+nonceBytes])
		pos += nonceBytes
	}
	balanceBytes := int(enc[pos])
	pos++
	if balanceBytes > 0 {
		a.Balance.SetBytes(enc[pos : pos+balanceBytes])
		pos += balanceBytes
	}
	codeHashBytes := int(enc[pos])
	pos++
	if codeHashBytes > 0 {
		var codeHashValue common.Hash
		copy(codeHashValue[:], enc[pos:pos+codeHashBytes])
		a.CodeHash = InternCodeHash(codeHashValue)
		pos += codeHashBytes
	}
	if pos >= len(enc) {
		return fmt.Errorf("deserialse2: %d >= %d ", pos, len(enc))
	}
	incBytes := int(enc[pos])
	pos++
	if incBytes > 0 {
		a.Incarnation = common.BytesToUint64(enc[pos : pos+incBytes])
	}
	return nil
}

func SerialiseV3(a *Account) []byte {
	var l int
	l++
	if a.Nonce > 0 {
		l += common.BitLenToByteLen(bits.Len64(a.Nonce))
	}
	l++
	if !a.Balance.IsZero() {
		l += a.Balance.ByteLen()
	}
	l++
	if !a.IsEmptyCodeHash() {
		l += 32
	}
	l++
	if a.Incarnation > 0 {
		l += common.BitLenToByteLen(bits.Len64(a.Incarnation))
	}
	value := make([]byte, l)
	pos := 0
	if a.Nonce == 0 {
		value[pos] = 0
		pos++
	} else {
		nonceBytes := common.BitLenToByteLen(bits.Len64(a.Nonce))
		value[pos] = byte(nonceBytes)
		var nonce = a.Nonce
		for i := nonceBytes; i > 0; i-- {
			value[pos+i] = byte(nonce)
			nonce >>= 8
		}
		pos += nonceBytes + 1
	}
	if a.Balance.IsZero() {
		value[pos] = 0
		pos++
	} else {
		balanceBytes := a.Balance.ByteLen()
		value[pos] = byte(balanceBytes)
		pos++
		a.Balance.WriteToSlice(value[pos : pos+balanceBytes])
		pos += balanceBytes
	}
	if a.IsEmptyCodeHash() {
		value[pos] = 0
		pos++
	} else {
		value[pos] = 32
		pos++
		codeHashValue := a.CodeHash.Value()
		copy(value[pos:pos+32], codeHashValue[:])
		pos += 32
	}
	if a.Incarnation == 0 {
		value[pos] = 0
	} else {
		incBytes := common.BitLenToByteLen(bits.Len64(a.Incarnation))
		value[pos] = byte(incBytes)
		var inc = a.Incarnation
		for i := incBytes; i > 0; i-- {
			value[pos+i] = byte(inc)
			inc >>= 8
		}
	}
	return value
}

func SerialiseV3Len(a *Account) (l int) {
	l++
	if a.Nonce > 0 {
		l += common.BitLenToByteLen(bits.Len64(a.Nonce))
	}
	l++
	if !a.Balance.IsZero() {
		l += a.Balance.ByteLen()
	}
	l++
	if !a.IsEmptyCodeHash() {
		l += 32
	}
	l++
	if a.Incarnation > 0 {
		l += common.BitLenToByteLen(bits.Len64(a.Incarnation))
	}
	return l
}

func SerialiseV3To(a *Account, value []byte) {
	pos := 0
	if a.Nonce == 0 {
		value[pos] = 0
		pos++
	} else {
		nonceBytes := common.BitLenToByteLen(bits.Len64(a.Nonce))
		value[pos] = byte(nonceBytes)
		var nonce = a.Nonce
		for i := nonceBytes; i > 0; i-- {
			value[pos+i] = byte(nonce)
			nonce >>= 8
		}
		pos += nonceBytes + 1
	}
	if a.Balance.IsZero() {
		value[pos] = 0
		pos++
	} else {
		balanceBytes := a.Balance.ByteLen()
		value[pos] = byte(balanceBytes)
		pos++
		a.Balance.WriteToSlice(value[pos : pos+balanceBytes])
		pos += balanceBytes
	}
	if a.IsEmptyCodeHash() {
		value[pos] = 0
		pos++
	} else {
		value[pos] = 32
		pos++
		codeHashValue := a.CodeHash.Value()
		copy(value[pos:pos+32], codeHashValue[:])
		pos += 32
	}
	if a.Incarnation == 0 {
		value[pos] = 0
	} else {
		incBytes := common.BitLenToByteLen(bits.Len64(a.Incarnation))
		value[pos] = byte(incBytes)
		var inc = a.Incarnation
		for i := incBytes; i > 0; i-- {
			value[pos+i] = byte(inc)
			inc >>= 8
		}
	}
}

// Decode the sender's balance and nonce from encoded byte-slice
func DecodeSender(enc []byte) (nonce uint64, balance uint256.Int, err error) {
	if len(enc) == 0 {
		return
	}

	var fieldSet = enc[0]
	var pos = 1

	if fieldSet&1 > 0 {
		decodeLength := int(enc[pos])

		if len(enc) < pos+decodeLength+1 {
			return nonce, balance, fmt.Errorf(
				"malformed CBOR for Account.Nonce: %s, Length %d",
				enc[pos+1:], decodeLength)
		}

		nonce = common.BytesToUint64(enc[pos+1 : pos+decodeLength+1])
		pos += decodeLength + 1
	}

	if fieldSet&2 > 0 {
		decodeLength := int(enc[pos])

		if len(enc) < pos+decodeLength+1 {
			return nonce, balance, fmt.Errorf(
				"malformed CBOR for Account.Nonce: %s, Length %d",
				enc[pos+1:], decodeLength)
		}

		(&balance).SetBytes(enc[pos+1 : pos+decodeLength+1])
	}
	return
}
