package common

import (
	"fmt"
	gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
	"github.com/ethereum/go-ethereum/common"
	"github.com/libp2p/go-libp2p-core/peer"
	"sync"
)

// MaxGuardianCount specifies the maximum number of guardians supported by on-chain contracts.
//
// Matching constants:
//  - MAX_LEN_GUARDIAN_KEYS in Solana contract (limited by transaction size - 19 is the maximum amount possible)
//
// The Eth and Terra contracts do not specify a maximum number and support more than that,
// but presumably, chain-specific transaction size limits will apply at some point (untested).
const MaxGuardianCount = 19

// MaxNodesPerGuardian specifies the maximum amount of nodes per guardian key that we'll accept
// whenever we maintain any per-guardian, per-node state.
//
// There currently isn't any state clean up, so the value is on the high side to prevent
// accidentally reaching the limit due to operational mistakes.
const MaxNodesPerGuardian = 15

type GuardianSet struct {
	// Guardian's public key hashes truncated by the ETH standard hashing mechanism (20 bytes).
	Keys []common.Address
	// On-chain set index
	Index uint32
}

func (g *GuardianSet) KeysAsHexStrings() []string {
	r := make([]string, len(g.Keys))

	for n, k := range g.Keys {
		r[n] = k.Hex()
	}

	return r
}

// KeyIndex returns a given address index from the guardian set. Returns (-1, false)
// if the address wasn't found and (addr, true) otherwise.
func (g *GuardianSet) KeyIndex(addr common.Address) (int, bool) {
	for n, k := range g.Keys {
		if k == addr {
			return n, true
		}
	}

	return -1, false
}

type GuardianSetState struct {
	mu      sync.Mutex
	current *GuardianSet

	// Last heartbeat message received per guardian per p2p node. Maintained
	// across guardian set updates - these values don't change.
	lastHeartbeats map[common.Address]map[peer.ID]*gossipv1.Heartbeat
}

func NewGuardianSetState() *GuardianSetState {
	return &GuardianSetState{
		lastHeartbeats: map[common.Address]map[peer.ID]*gossipv1.Heartbeat{},
	}
}

func (st *GuardianSetState) Set(set *GuardianSet) {
	st.mu.Lock()
	defer st.mu.Unlock()

	st.current = set
}

func (st *GuardianSetState) Get() *GuardianSet {
	st.mu.Lock()
	defer st.mu.Unlock()

	return st.current
}

// LastHeartbeat returns the most recent heartbeat message received for
// a given guardian node, or nil if none have been received.
func (st *GuardianSetState) LastHeartbeat(addr common.Address) map[peer.ID]*gossipv1.Heartbeat {
	st.mu.Lock()
	defer st.mu.Unlock()
	ret := make(map[peer.ID]*gossipv1.Heartbeat)
	for k, v := range st.lastHeartbeats[addr] {
		ret[k] = v
	}
	return ret
}

// SetHeartbeat stores a verified heartbeat observed by a given guardian.
func (st *GuardianSetState) SetHeartbeat(addr common.Address, peerId peer.ID, hb *gossipv1.Heartbeat) error {
	st.mu.Lock()
	defer st.mu.Unlock()

	v, ok := st.lastHeartbeats[addr]

	if !ok {
		v = make(map[peer.ID]*gossipv1.Heartbeat)
		st.lastHeartbeats[addr] = v
	} else {
		if len(v) >= MaxNodesPerGuardian {
			// TODO: age out old entries?
			return fmt.Errorf("too many nodes (%d) for guardian, cannot store entry", len(v))
		}
	}

	v[peerId] = hb
	return nil
}

// GetAll returns all stored heartbeats.
func (st *GuardianSetState) GetAll() map[common.Address]map[peer.ID]*gossipv1.Heartbeat {
	st.mu.Lock()
	defer st.mu.Unlock()

	ret := make(map[common.Address]map[peer.ID]*gossipv1.Heartbeat)

	// Deep copy
	for addr, v := range st.lastHeartbeats {
		ret[addr] = make(map[peer.ID]*gossipv1.Heartbeat)
		for peerId, hb := range v {
			ret[addr][peerId] = hb
		}
	}

	return ret
}
