package discovery

import (
	"errors"
	"net"
	"sync"
	"sync/atomic"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/wire"
	"github.com/lightningnetwork/lnd/lnpeer"
	"github.com/lightningnetwork/lnd/lnwire"
)

// mockPeer implements the lnpeer.Peer interface and is used to test the
// gossiper's interaction with peers.
type mockPeer struct {
	pk           *btcec.PublicKey
	sentMsgs     chan lnwire.Message
	quit         chan struct{}
	disconnected atomic.Bool
}

var _ lnpeer.Peer = (*mockPeer)(nil)

func (p *mockPeer) SendMessage(_ bool, msgs ...lnwire.Message) error {
	if p.sentMsgs == nil && p.quit == nil {
		return nil
	}

	for _, msg := range msgs {
		select {
		case p.sentMsgs <- msg:
		case <-p.quit:
			return errors.New("peer disconnected")
		}
	}

	return nil
}

func (p *mockPeer) SendMessageLazy(sync bool, msgs ...lnwire.Message) error {
	return p.SendMessage(sync, msgs...)
}

func (p *mockPeer) AddNewChannel(_ *lnpeer.NewChannel,
	_ <-chan struct{}) error {

	return nil
}
func (p *mockPeer) WipeChannel(_ *wire.OutPoint)  {}
func (p *mockPeer) IdentityKey() *btcec.PublicKey { return p.pk }
func (p *mockPeer) PubKey() [33]byte {
	var pubkey [33]byte
	copy(pubkey[:], p.pk.SerializeCompressed())
	return pubkey
}
func (p *mockPeer) Address() net.Addr { return nil }
func (p *mockPeer) QuitSignal() <-chan struct{} {
	return p.quit
}
func (p *mockPeer) LocalFeatures() *lnwire.FeatureVector {
	return nil
}
func (p *mockPeer) RemoteFeatures() *lnwire.FeatureVector {
	return nil
}

func (p *mockPeer) AddPendingChannel(_ lnwire.ChannelID,
	_ <-chan struct{}) error {

	return nil
}

func (p *mockPeer) RemovePendingChannel(_ lnwire.ChannelID) error {
	return nil
}

func (p *mockPeer) Disconnect(err error) {
	p.disconnected.Store(true)
}

// mockMessageStore is an in-memory implementation of the MessageStore interface
// used for the gossiper's unit tests.
type mockMessageStore struct {
	sync.Mutex
	messages map[[33]byte]map[lnwire.Message]struct{}
}

func newMockMessageStore() *mockMessageStore {
	return &mockMessageStore{
		messages: make(map[[33]byte]map[lnwire.Message]struct{}),
	}
}

var _ GossipMessageStore = (*mockMessageStore)(nil)

func (s *mockMessageStore) AddMessage(msg lnwire.Message, pubKey [33]byte) error {
	s.Lock()
	defer s.Unlock()

	if _, ok := s.messages[pubKey]; !ok {
		s.messages[pubKey] = make(map[lnwire.Message]struct{})
	}

	s.messages[pubKey][msg] = struct{}{}

	return nil
}

func (s *mockMessageStore) DeleteMessage(msg lnwire.Message, pubKey [33]byte) error {
	s.Lock()
	defer s.Unlock()

	peerMsgs, ok := s.messages[pubKey]
	if !ok {
		return nil
	}

	delete(peerMsgs, msg)
	return nil
}

func (s *mockMessageStore) Messages() (map[[33]byte][]lnwire.Message, error) {
	s.Lock()
	defer s.Unlock()

	msgs := make(map[[33]byte][]lnwire.Message, len(s.messages))
	for peer, peerMsgs := range s.messages {
		for msg := range peerMsgs {
			msgs[peer] = append(msgs[peer], msg)
		}
	}
	return msgs, nil
}

func (s *mockMessageStore) Peers() (map[[33]byte]struct{}, error) {
	s.Lock()
	defer s.Unlock()

	peers := make(map[[33]byte]struct{}, len(s.messages))
	for peer := range s.messages {
		peers[peer] = struct{}{}
	}
	return peers, nil
}

func (s *mockMessageStore) MessagesForPeer(pubKey [33]byte) ([]lnwire.Message, error) {
	s.Lock()
	defer s.Unlock()

	peerMsgs, ok := s.messages[pubKey]
	if !ok {
		return nil, nil
	}

	msgs := make([]lnwire.Message, 0, len(peerMsgs))
	for msg := range peerMsgs {
		msgs = append(msgs, msg)
	}

	return msgs, nil
}

type mockScidCloser struct {
	m           map[lnwire.ShortChannelID]struct{}
	channelPeer bool

	sync.Mutex
}

func newMockScidCloser(channelPeer bool) *mockScidCloser {
	return &mockScidCloser{
		m:           make(map[lnwire.ShortChannelID]struct{}),
		channelPeer: channelPeer,
	}
}

func (m *mockScidCloser) PutClosedScid(scid lnwire.ShortChannelID) error {
	m.Lock()
	m.m[scid] = struct{}{}
	m.Unlock()

	return nil
}

func (m *mockScidCloser) IsClosedScid(scid lnwire.ShortChannelID) (bool,
	error) {

	m.Lock()
	defer m.Unlock()

	_, ok := m.m[scid]

	return ok, nil
}

func (m *mockScidCloser) IsChannelPeer(pubkey *btcec.PublicKey) (bool, error) {
	return m.channelPeer, nil
}
