package sync

import (
	"io"

	"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p"
	"github.com/OffchainLabs/prysm/v7/config/params"
	"github.com/libp2p/go-libp2p/core/network"
	"github.com/libp2p/go-libp2p/core/protocol"
	"github.com/pkg/errors"
)

// Specifies the fixed size context length.
const forkDigestLength = 4

// writes peer's current context for the expected payload to the stream.
func writeContextToStream(objCtx []byte, stream network.Stream) error {
	// The rpc context for our v2 methods is the fork-digest of
	// the relevant payload. We write the associated fork-digest(context)
	// into the stream for the payload.
	rpcCtx, err := expectRpcContext(stream)
	if err != nil {
		return err
	}
	// Exit early if an empty context is expected.
	if !rpcCtx {
		return nil
	}
	_, err = stream.Write(objCtx)
	return err
}

// reads any attached context-bytes to the payload.
func readContextFromStream(stream network.Stream) ([]byte, error) {
	hasCtx, err := expectRpcContext(stream)
	if err != nil {
		return nil, err
	}
	if !hasCtx {
		return []byte{}, nil
	}
	// Read context (fork-digest) from stream
	b := make([]byte, forkDigestLength)
	if _, err := io.ReadFull(stream, b); err != nil {
		return nil, err
	}
	return b, nil
}

func expectRpcContext(stream network.Stream) (bool, error) {
	_, message, version, err := p2p.TopicDeconstructor(string(stream.Protocol()))
	if err != nil {
		return false, err
	}
	// For backwards compatibility, we want to omit context bytes for certain v1 methods that were defined before
	// context bytes were introduced into the protocol.
	if version == p2p.SchemaVersionV1 && p2p.OmitContextBytesV1[message] {
		return false, nil
	}
	return true, nil
}

// Minimal interface for a stream with a protocol.
type withProtocol interface {
	Protocol() protocol.ID
}

// Validates that the rpc topic matches the provided version.
func validateVersion(version string, stream withProtocol) error {
	_, _, streamVersion, err := p2p.TopicDeconstructor(string(stream.Protocol()))
	if err != nil {
		return err
	}
	if streamVersion != version {
		return errors.Errorf("stream version of %s doesn't match provided version %s", streamVersion, version)
	}
	return nil
}

// ContextByteVersions is a mapping between expected values for context bytes
// and the runtime/version identifier they correspond to. This can be used to look up the type
// needed to unmarshal a wire-encoded value.
type ContextByteVersions map[[4]byte]int

// ContextByteVersionsForValRoot computes a mapping between all possible context bytes values
// and the runtime/version identifier for the corresponding fork.
func ContextByteVersionsForValRoot(valRoot [32]byte) (ContextByteVersions, error) {
	m := make(ContextByteVersions)
	for _, entry := range params.SortedNetworkScheduleEntries() {
		m[entry.ForkDigest] = entry.VersionEnum
	}
	return m, nil
}
