package behavior

import (
	"errors"

	tmsync "github.com/tendermint/tendermint/libs/sync"
	"github.com/tendermint/tendermint/p2p"
)

// Reporter provides an interface for reactors to report the behavior
// of peers synchronously to other components.
type Reporter interface {
	Report(behavior PeerBehavior) error
}

// SwitchReporter reports peer behavior to an internal Switch.
type SwitchReporter struct {
	sw *p2p.Switch
}

// NewSwitchReporter return a new SwitchReporter instance which wraps the Switch.
func NewSwitchReporter(sw *p2p.Switch) *SwitchReporter {
	return &SwitchReporter{
		sw: sw,
	}
}

// Report reports the behavior of a peer to the Switch.
func (spbr *SwitchReporter) Report(behavior PeerBehavior) error {
	peer := spbr.sw.Peers().Get(behavior.peerID)
	if peer == nil {
		return errors.New("peer not found")
	}

	switch reason := behavior.reason.(type) {
	case consensusVote, blockPart:
		spbr.sw.MarkPeerAsGood(peer)
	case badMessage:
		spbr.sw.StopPeerForError(peer, reason.explanation)
	case messageOutOfOrder:
		spbr.sw.StopPeerForError(peer, reason.explanation)
	default:
		return errors.New("unknown reason reported")
	}

	return nil
}

// MockReporter is a concrete implementation of the Reporter
// interface used in reactor tests to ensure reactors report the correct
// behavior in manufactured scenarios.
type MockReporter struct {
	mtx tmsync.RWMutex
	pb  map[p2p.NodeID][]PeerBehavior
}

// NewMockReporter returns a Reporter which records all reported
// behaviors in memory.
func NewMockReporter() *MockReporter {
	return &MockReporter{
		pb: map[p2p.NodeID][]PeerBehavior{},
	}
}

// Report stores the PeerBehavior produced by the peer identified by peerID.
func (mpbr *MockReporter) Report(behavior PeerBehavior) error {
	mpbr.mtx.Lock()
	defer mpbr.mtx.Unlock()
	mpbr.pb[behavior.peerID] = append(mpbr.pb[behavior.peerID], behavior)

	return nil
}

// GetBehaviors returns all behaviors reported on the peer identified by peerID.
func (mpbr *MockReporter) GetBehaviors(peerID p2p.NodeID) []PeerBehavior {
	mpbr.mtx.RLock()
	defer mpbr.mtx.RUnlock()
	if items, ok := mpbr.pb[peerID]; ok {
		result := make([]PeerBehavior, len(items))
		copy(result, items)

		return result
	}

	return []PeerBehavior{}
}
