// Copyright 2014 The go-ethereum Authors
// (original work)
// Copyright 2024 The Erigon Authors
// (modifications)
// 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 types

import (
	"fmt"
	"io"
	"slices"

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

//(go:generate gencodec -type Log -field-override logMarshaling -out gen_log_json.go)

// Log represents a contract log event. These events are generated by the LOG opcode and
// stored/indexed by the node.
type Log struct {
	// Consensus fields:
	// address of the contract that generated the event
	Address common.Address `json:"address" gencodec:"required" codec:"1"`
	// list of topics provided by the contract.
	Topics []common.Hash `json:"topics" gencodec:"required" codec:"2"`
	// supplied by the contract, usually ABI-encoded
	Data []byte `json:"data" gencodec:"required" codec:"3"`

	// Derived fields. These fields are filled in by the node
	// but not secured by consensus.
	// block in which the transaction was included
	BlockNumber uint64 `json:"blockNumber" codec:"-"`

	// hash of the transaction
	TxHash common.Hash `json:"transactionHash" gencodec:"required" codec:"-"`
	// index of the transaction in the block
	TxIndex uint `json:"transactionIndex" codec:"-"`
	// hash of the block in which the transaction was included
	BlockHash common.Hash `json:"blockHash" codec:"-"`
	// index of the log in the block
	Index uint `json:"logIndex" codec:"-"`

	// The Removed field is true if this log was reverted due to a chain reorganisation.
	// You must pay attention to this field if you receive logs through a filter query.
	Removed bool `json:"removed" codec:"-"`
}

type Logs []*Log

type ErigonLog struct {
	Log
	Timestamp uint64 `json:"timestamp" codec:"-"`
}

type ErigonLogs []*ErigonLog

// ToErigonLogs converts Logs to ErigonLogs, adding a timestamp to each entry.
func (logs Logs) ToErigonLogs(timestamp uint64) ErigonLogs {
	result := make(ErigonLogs, len(logs))
	for i, l := range logs {
		result[i] = &ErigonLog{
			Log:       *l,
			Timestamp: timestamp,
		}
	}
	return result
}

// RPCLog Extends `types.Log` and add BlockTimestamp field
type RPCLog struct {
	Log
	BlockTimestamp uint64 `json:"blockTimestamp" codec:"-"`
}

type RPCLogs []*RPCLog

func (logs Logs) Copy() Logs {
	if logs == nil {
		return nil
	}
	logsCopy := make(Logs, len(logs))
	for i, log := range logs {
		logsCopy[i] = log.Copy()
	}
	return logsCopy
}

// ToRPCTransactionLog converts types.Log in a RPCLog.
func ToRPCTransactionLog(log *Log, header *Header, txHash common.Hash, txIndex uint64) *RPCLog {
	return &RPCLog{
		Log:            *log,
		BlockTimestamp: header.Time,
	}
}

func (logs Logs) Filter(addrMap map[common.Address]struct{}, topics [][]common.Hash, maxLogs uint64) Logs {
	topicMap := make([]map[common.Hash]struct{}, len(topics))

	//populate topic map
	for idx, v := range topics {
		topicMap[idx] = make(map[common.Hash]struct{})
		for _, vv := range v {
			topicMap[idx][vv] = struct{}{}
		}
	}

	o := make(Logs, 0, len(logs))
	logCount := uint64(0)
	for _, v := range logs {
		// check address if addrMap is not empty
		if len(addrMap) != 0 {
			if _, ok := addrMap[v.Address]; !ok {
				// not there? skip this log
				continue
			}
		}

		// If the to filtered topics is greater than the amount of topics in logs, skip.
		if len(topics) > len(v.Topics) {
			continue
		}
		// the default state is to include the log
		found := true
		// if there are no topics provided, then match all
		for idx, topicSet := range topicMap {
			// if the topicSet is empty, match all as wildcard
			if len(topicSet) == 0 {
				continue
			}
			// the topicSet isnt empty, so the topic must be included.
			if _, ok := topicSet[v.Topics[idx]]; !ok {
				// the topic wasn't found, so we should skip this log
				found = false
				break
			}
		}
		if found {
			o = append(o, v)
		}

		logCount += 1
		if maxLogs != 0 && logCount >= maxLogs {
			break
		}
	}
	return o
}

func (logs Logs) ContainingTopics(addrMap map[common.Address]struct{}, topicsMap map[common.Hash]struct{}, maxLogs uint64) Logs {
	o := make(Logs, 0, len(logs))
	var logCount uint64

	for _, v := range logs {
		found := false

		// check address if addrMap is not empty
		if _, requested := addrMap[v.Address]; !requested && len(addrMap) > 0 {
			continue // not there? skip this log
		}
		//topicsMap len zero match any topics
		if len(topicsMap) == 0 {
			o = append(o, v)
		} else {
			for i := range v.Topics {
				//Contain any topics that matched
				if _, ok := topicsMap[v.Topics[i]]; ok {
					found = true
				}
			}
			if found {
				o = append(o, v)
			}
		}
		logCount += 1
		if maxLogs != 0 && logCount >= maxLogs {
			break
		}
	}
	return o
}

type logMarshaling struct {
	Data        hexutil.Bytes
	BlockNumber hexutil.Uint64
	TxIndex     hexutil.Uint
	Index       hexutil.Uint
}

type rlpLog struct {
	Address common.Address
	Topics  []common.Hash
	Data    []byte
}

type rlpStorageLog struct {
	Address common.Address
	Topics  []common.Hash
	Data    []byte
	//BlockNumber uint64
	//TxHash common.Hash
	//TxIndex uint
	//BlockHash   common.Hash
	//Index uint
}

// EncodeRLP implements rlp.Encoder.
func (l *Log) EncodeRLP(w io.Writer) error {
	return rlp.Encode(w, rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data})
}

// DecodeRLP implements rlp.Decoder.
func (l *Log) DecodeRLP(s *rlp.Stream) error {
	var dec rlpLog
	err := s.Decode(&dec)
	if err == nil {
		l.Address, l.Topics, l.Data = dec.Address, dec.Topics, dec.Data
	}
	return err
}

// Copy creates a deep copy of the Log.
func (l *Log) Copy() *Log {
	if l == nil {
		return nil
	}
	return &Log{
		Address:     l.Address,
		Topics:      slices.Clone(l.Topics),
		Data:        slices.Clone(l.Data),
		BlockNumber: l.BlockNumber,
		TxHash:      l.TxHash,
		TxIndex:     l.TxIndex,
		BlockHash:   l.BlockHash,
		Index:       l.Index,
		Removed:     l.Removed,
	}
}

// LogForStorage is a wrapper around a Log that flattens and parses the entire content of
// a log including non-consensus fields.
type LogForStorage Log

// EncodeRLP implements rlp.Encoder.
func (l *LogForStorage) EncodeRLP(w io.Writer) error {
	return rlp.Encode(w, rlpStorageLog{
		Address: l.Address,
		Topics:  l.Topics,
		Data:    l.Data,
	})
}

func decodeTopics2(s *rlp.Stream) (list []common.Hash, err error) {
	l, err := s.List()
	if err != nil {
		return nil, err
	}
	if l == 0 {
		return []common.Hash{}, s.ListEnd()
	}
	listLen := int(l / (1 + 32))  // rlpLenPrefix+32bytes
	preAlloc := min(128, listLen) // attacker may craft rlp prefix - which will trigger hube pre-alloc. so, add hard-limit
	list = make([]common.Hash, 0, preAlloc)
	var i int
	var b common.Hash
	for ; s.MoreDataInList(); i++ {
		if err = s.ReadBytes(b[:]); err != nil {
			return nil, err
		}
		list = append(list, b)
	}
	return list[:i], s.ListEnd()
}

// DecodeRLP implements rlp.Decoder.
//
// Note some redundant fields(e.g. block number, txn hash etc) will be assembled later.
func (l *LogForStorage) DecodeRLP(s *rlp.Stream) error {
	_, err := s.List()
	if err != nil {
		return err
	}
	err = s.ReadBytes(l.Address[:])
	if err != nil {
		return fmt.Errorf("read Address: %w", err)
	}
	l.Topics, err = decodeTopics2(s)
	if err != nil {
		return err
	}
	if l.Data, err = s.Bytes(); err != nil {
		return fmt.Errorf("read Data: %w", err)
	}
	return s.ListEnd()
}
