// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library 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.
//
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package eth

import (
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/lru"
	"github.com/ethereum/go-ethereum/eth/protocols/eth"
	"github.com/ethereum/go-ethereum/eth/protocols/snap"
)

const (
	// maxKnownVotes is the maximum vote hashes to keep per peer.
	maxKnownVotes = 32768
	// maxKnownTimeouts is the maximum timeout hashes to keep per peer.
	maxKnownTimeouts = 32768
	// maxKnownSyncInfos is the maximum sync-info hashes to keep per peer.
	maxKnownSyncInfos = 32768
)

// ethPeerInfo represents a short summary of the `eth` sub-protocol metadata known
// about a connected peer.
type ethPeerInfo struct {
	Version    uint        `json:"version"`    // Ethereum protocol version negotiated
	Difficulty *big.Int    `json:"difficulty"` // Total difficulty of the peer's best block
	Head       common.Hash `json:"head"`       // Hash of the peer's best block
	Number     uint64      `json:"number"`     // Number of the peer's best block
	*peerBlockRange
}

type peerBlockRange struct {
	Earliest   uint64      `json:"earliestBlock"`
	Latest     uint64      `json:"latestBlock"`
	LatestHash common.Hash `json:"latestBlockHash"`
}

// ethPeer is a wrapper around eth.Peer to maintain a few extra metadata.
type ethPeer struct {
	*eth.Peer
	snapExt *snapPeer // Satellite `snap` connection

	// XDC consensus dedup: LRU caches tracking recently seen message hashes
	// per peer to prevent duplicate broadcast loops (Issue #272).
	knownVotes     *lru.Cache[common.Hash, struct{}]
	knownTimeouts  *lru.Cache[common.Hash, struct{}]
	knownSyncInfos *lru.Cache[common.Hash, struct{}]
}

// info gathers and returns some `eth` protocol metadata known about a peer.
func (p *ethPeer) info() *ethPeerInfo {
	info := &ethPeerInfo{Version: p.Version()}
	if hash, td := p.Head(); hash != (common.Hash{}) {
		info.Head = hash
		info.Difficulty = td
		// Issue #538: XDC is pre-merge; estimateXDTDP encodes the peer's head
		// block number into the TD field on the wire (handshake.go:42-48).
		// Recover it for `admin.peers[*].protocols.eth.number` so operators
		// see the peer's actual head instead of a stale zero from the old
		// "TODO" placeholder. Falls back to 0 if TD is missing or doesn't
		// fit a uint64.
		if td != nil && td.IsUint64() {
			info.Number = td.Uint64()
		}
	}
	if br := p.BlockRange(); br != nil {
		info.peerBlockRange = &peerBlockRange{
			Earliest:   br.EarliestBlock,
			Latest:     br.LatestBlock,
			LatestHash: br.LatestBlockHash,
		}
	}
	return info
}

// KnownVote reports whether the peer is known to already have a vote with
// the given hash.
func (p *ethPeer) KnownVote(hash common.Hash) bool {
	return p.knownVotes.Contains(hash)
}

// MarkVote marks a vote as known for the peer so it won't be propagated
// again.
func (p *ethPeer) MarkVote(hash common.Hash) {
	p.knownVotes.Add(hash, struct{}{})
}

// KnownTimeout reports whether the peer is known to already have a timeout
// with the given hash.
func (p *ethPeer) KnownTimeout(hash common.Hash) bool {
	return p.knownTimeouts.Contains(hash)
}

// MarkTimeout marks a timeout as known for the peer so it won't be
// propagated again.
func (p *ethPeer) MarkTimeout(hash common.Hash) {
	p.knownTimeouts.Add(hash, struct{}{})
}

// KnownSyncInfo reports whether the peer is known to already have a sync
// info with the given hash.
func (p *ethPeer) KnownSyncInfo(hash common.Hash) bool {
	return p.knownSyncInfos.Contains(hash)
}

// MarkSyncInfo marks a sync info as known for the peer so it won't be
// propagated again.
func (p *ethPeer) MarkSyncInfo(hash common.Hash) {
	p.knownSyncInfos.Add(hash, struct{}{})
}

// snapPeerInfo represents a short summary of the `snap` sub-protocol metadata known
// about a connected peer.
type snapPeerInfo struct {
	Version uint `json:"version"` // Snapshot protocol version negotiated
}

// snapPeer is a wrapper around snap.Peer to maintain a few extra metadata.
type snapPeer struct {
	*snap.Peer
}

// info gathers and returns some `snap` protocol metadata known about a peer.
func (p *snapPeer) info() *snapPeerInfo {
	return &snapPeerInfo{
		Version: p.Version(),
	}
}
