package eth

import (
	"fmt"

	"github.com/ethereum/go-ethereum/core"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/eth/protocols/bsc"
	"github.com/ethereum/go-ethereum/p2p/enode"
)

// bscHandler implements the bsc.Backend interface to handle the various network
// packets that are sent as broadcasts.
type bscHandler handler

func (h *bscHandler) Chain() *core.BlockChain { return h.chain }

// RunPeer is invoked when a peer joins on the `bsc` protocol.
func (h *bscHandler) RunPeer(peer *bsc.Peer, hand bsc.Handler) error {
	if err := peer.Handshake(); err != nil {
		// ensure that waitBscExtension receives the exit signal normally
		// otherwise, can't graceful shutdown
		ps := h.peers
		id := peer.ID()

		// Ensure nobody can double connect
		ps.lock.Lock()
		if wait, ok := ps.bscWait[id]; ok {
			delete(ps.bscWait, id)
			peer.Log().Error("Bsc extension Handshake failed", "err", err)
			wait <- nil
		}
		ps.lock.Unlock()
		return err
	}
	return (*handler)(h).runBscExtension(peer, hand)
}

// PeerInfo retrieves all known `bsc` information about a peer.
func (h *bscHandler) PeerInfo(id enode.ID) interface{} {
	if p := h.peers.peer(id.String()); p != nil && p.bscExt != nil {
		return p.bscExt.info()
	}
	return nil
}

// Handle is invoked from a peer's message handler when it receives a new remote
// message that the handler couldn't consume and serve itself.
func (h *bscHandler) Handle(peer *bsc.Peer, packet bsc.Packet) error {
	// DeliverSnapPacket is invoked from a peer's message handler when it transmits a
	// data packet for the local node to consume.
	switch packet := packet.(type) {
	case *bsc.VotesPacket:
		return h.handleVotesBroadcast(peer, packet.Votes)

	default:
		return fmt.Errorf("unexpected bsc packet type: %T", packet)
	}
}

// handleVotesBroadcast is invoked from a peer's message handler when it transmits a
// votes broadcast for the local node to process.
func (h *bscHandler) handleVotesBroadcast(peer *bsc.Peer, votes []*types.VoteEnvelope) error {
	if peer.IsOverLimitAfterReceiving() {
		return nil
	}
	// Here we only put the first vote, to avoid ddos attack by sending a large batch of votes.
	// This won't abandon any valid vote, because one vote is sent every time referring to func voteBroadcastLoop
	if len(votes) > 0 {
		h.votepool.PutVote(votes[0])
	}

	return nil
}
