package remote_web3signer

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"maps"
	"os"
	"path/filepath"
	"slices"
	"strings"
	"sync"
	"time"

	"github.com/OffchainLabs/prysm/v6/async/event"
	fieldparams "github.com/OffchainLabs/prysm/v6/config/fieldparams"
	"github.com/OffchainLabs/prysm/v6/crypto/bls"
	"github.com/OffchainLabs/prysm/v6/encoding/bytesutil"
	"github.com/OffchainLabs/prysm/v6/io/file"
	"github.com/OffchainLabs/prysm/v6/monitoring/tracing/trace"
	validatorpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1/validator-client"
	"github.com/OffchainLabs/prysm/v6/runtime/version"
	"github.com/OffchainLabs/prysm/v6/validator/accounts/petnames"
	"github.com/OffchainLabs/prysm/v6/validator/keymanager"
	"github.com/OffchainLabs/prysm/v6/validator/keymanager/remote-web3signer/internal"
	"github.com/OffchainLabs/prysm/v6/validator/keymanager/remote-web3signer/types"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/fsnotify/fsnotify"
	"github.com/go-playground/validator/v10"
	"github.com/logrusorgru/aurora"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

const (
	maxRetries = 60
	retryDelay = 10 * time.Second
)

// SetupConfig includes configuration values for initializing.
// a keymanager, such as passwords, the wallet, and more.
// Web3Signer contains one public keys option. Either through a URL or a static key list.
type SetupConfig struct {
	KeyFilePath           string
	BaseEndpoint          string
	GenesisValidatorsRoot []byte

	// Either URL or keylist must be set.
	// If the URL is set, the keymanager will fetch the public keys from the URL.
	// caution: this option is susceptible to slashing if the web3signer's validator keys are shared across validators
	PublicKeysURL string

	// Either URL or keylist must be set.
	// a static list of public keys to be passed by the user to determine what accounts should sign.
	// This will provide a layer of safety against slashing if the web3signer is shared across validators.
	ProvidedPublicKeys []string
}

// Keymanager defines the web3signer keymanager.
type Keymanager struct {
	client                internal.HttpSignerClient
	genesisValidatorsRoot []byte
	providedPublicKeys    [][48]byte          // (source of truth) flag loaded + file loaded + api loaded keys
	flagLoadedKeysMap     map[string][48]byte // stores what was provided from flag ( as opposed to from file )
	accountsChangedFeed   *event.Feed
	validator             *validator.Validate
	retriesRemaining      int
	keyFilePath           string
	lock                  sync.RWMutex
}

// NewKeymanager instantiates a new web3signer key manager.
func NewKeymanager(ctx context.Context, cfg *SetupConfig) (*Keymanager, error) {
	ctx, span := trace.StartSpan(ctx, "remote-keymanager.NewKeymanager")
	defer span.End()
	if cfg.BaseEndpoint == "" || !bytesutil.IsValidRoot(cfg.GenesisValidatorsRoot) {
		return nil, fmt.Errorf("invalid setup config, one or more configs are empty: BaseEndpoint: %v, GenesisValidatorsRoot: %#x", cfg.BaseEndpoint, cfg.GenesisValidatorsRoot)
	}
	client, err := internal.NewApiClient(cfg.BaseEndpoint)
	if err != nil {
		return nil, errors.Wrap(err, "could not create apiClient")
	}

	km := &Keymanager{
		client:                internal.HttpSignerClient(client),
		genesisValidatorsRoot: cfg.GenesisValidatorsRoot,
		accountsChangedFeed:   new(event.Feed),
		validator:             validator.New(),
		retriesRemaining:      maxRetries,
		keyFilePath:           cfg.KeyFilePath,
	}

	keyFileExists := false
	if km.keyFilePath != "" {
		keyFileExists, err = file.Exists(km.keyFilePath, file.Regular)
		if err != nil {
			return nil, errors.Wrapf(err, "could not check if remote signer persistent keys exist in %s", km.keyFilePath)
		}
		if !keyFileExists {
			return nil, fmt.Errorf("no file exists in remote signer key file path %s", km.keyFilePath)
		}
	}

	var ppk []string
	// load key values
	if cfg.PublicKeysURL != "" {
		providedPublicKeys, err := km.client.GetPublicKeys(ctx, cfg.PublicKeysURL)
		if err != nil {
			erroredResponsesTotal.Inc()
			return nil, errors.Wrapf(err, "could not get public keys from remote server URL %v", cfg.PublicKeysURL)
		}
		ppk = providedPublicKeys
	} else if len(cfg.ProvidedPublicKeys) != 0 {
		ppk = cfg.ProvidedPublicKeys
	}

	// use a map to remove duplicates
	flagLoadedKeys := make(map[string][48]byte)

	// Populate the map with existing keys
	for _, key := range ppk {
		decodedKey, err := hexutil.Decode(key)
		if err != nil {
			return nil, errors.Wrapf(err, "could not decode public key %s", key)
		}
		if len(decodedKey) != fieldparams.BLSPubkeyLength {
			return nil, fmt.Errorf("public key %s has invalid length (expected %d, got %d)", decodedKey, fieldparams.BLSPubkeyLength, len(decodedKey))
		}
		flagLoadedKeys[key] = bytesutil.ToBytes48(decodedKey)
	}
	km.flagLoadedKeysMap = flagLoadedKeys

	// load from file
	if keyFileExists {
		log.WithField("file", km.keyFilePath).Info("Loading keys from file")
		_, fileKeys, err := km.readKeyFile()
		if err != nil {
			return nil, errors.Wrap(err, "could not read key file")
		}
		if len(flagLoadedKeys) != 0 {
			log.WithField("flagLoadedKeyCount", len(flagLoadedKeys)).WithField("fileLoadedKeyCount", len(fileKeys)).Info("Combining flag loaded keys and file loaded keys.")
			maps.Copy(fileKeys, flagLoadedKeys)
			if err = km.savePublicKeysToFile(fileKeys); err != nil {
				return nil, errors.Wrap(err, "could not save public keys to file")
			}
		}
		km.lock.Lock()
		km.providedPublicKeys = slices.Collect(maps.Values(fileKeys))
		km.lock.Unlock()
		// create a file watcher
		go func() {
			err = km.refreshRemoteKeysFromFileChangesWithRetry(ctx, retryDelay)
			if err != nil {
				log.WithError(err).Error("Could not refresh remote keys from file changes")
			}
		}()
	} else {
		km.lock.Lock()
		km.providedPublicKeys = slices.Collect(maps.Values(flagLoadedKeys))
		km.lock.Unlock()
	}

	return km, nil
}

func (km *Keymanager) refreshRemoteKeysFromFileChangesWithRetry(ctx context.Context, retryDelay time.Duration) error {
	if ctx.Err() != nil {
		return ctx.Err()
	}
	if km.retriesRemaining == 0 {
		return errors.New("file check retries remaining exceeded")
	}
	err := km.refreshRemoteKeysFromFileChanges(ctx)
	if err != nil {
		km.updatePublicKeys(slices.Collect(maps.Values(km.flagLoadedKeysMap))) // update the keys to flag provided defaults
		km.retriesRemaining--
		log.WithError(err).Debug("Error occurred on key refresh")
		log.WithFields(logrus.Fields{"path": km.keyFilePath, "retriesRemaining": km.retriesRemaining, "retryDelay": retryDelay}).Warnf("Could not refresh keys. Retrying...")
		time.Sleep(retryDelay)
		return km.refreshRemoteKeysFromFileChangesWithRetry(ctx, retryDelay)
	}
	return nil
}

func (km *Keymanager) readKeyFile() ([][48]byte, map[string][48]byte, error) {
	km.lock.RLock()
	defer km.lock.RUnlock()

	if km.keyFilePath == "" {
		return nil, nil, errors.New("no key file path provided")
	}
	f, err := os.Open(filepath.Clean(km.keyFilePath))
	if err != nil {
		return nil, nil, errors.Wrap(err, "could not open remote signer public key file")
	}
	defer func() {
		if err := f.Close(); err != nil {
			log.WithError(err).Error("Could not close remote signer public key file")
		}
	}()
	// Use a map to track and skip duplicate lines
	seenKeys := make(map[string][48]byte)
	scanner := bufio.NewScanner(f)
	var keys [][48]byte
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		pubkeyLength := (fieldparams.BLSPubkeyLength * 2) + 2
		if line == "" {
			// skip empty line
			continue
		}
		// allow for pubkeys without the 0x
		if len(line) == pubkeyLength-2 && !strings.HasPrefix(line, "0x") {
			line = "0x" + line
		}
		if len(line) != pubkeyLength {
			log.WithFields(logrus.Fields{
				"filepath": km.keyFilePath,
				"key":      line,
			}).Error("Invalid public key in remote signer key file")
			continue
		}
		if _, found := seenKeys[line]; !found {
			// If it's a new line, mark it as seen and process it
			pubkey, err := hexutil.Decode(line)
			if err != nil {
				return nil, nil, errors.Wrapf(err, "could not decode public key %s in remote signer key file", line)
			}
			bPubkey := bytesutil.ToBytes48(pubkey)
			seenKeys[line] = bPubkey
			keys = append(keys, bPubkey)
		}
	}
	// Check for scanning errors
	if err := scanner.Err(); err != nil {
		return nil, nil, errors.Wrap(err, "could not scan remote signer public key file")
	}
	if len(keys) == 0 {
		log.Warn("Remote signer key file: no valid public keys found. Defaulting to flag provided keys if any exist.")
	}
	return keys, seenKeys, nil
}

func (km *Keymanager) savePublicKeysToFile(providedPublicKeys map[string][48]byte) error {
	if km.keyFilePath == "" {
		return errors.New("no key file provided")
	}
	pubkeys := make([][48]byte, 0)
	// Open the file with write and truncate permissions
	f, err := os.OpenFile(km.keyFilePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
	if err != nil {
		return fmt.Errorf("failed to open file: %w", err)
	}
	defer func(f *os.File) {
		err := f.Close()
		if err != nil {
			log.WithError(err).Error("Could not close file, proceeding without closing the file")
		}
	}(f)

	// Iterate through all lines in the slice and write them to the file
	for key, value := range providedPublicKeys {
		if _, err := f.WriteString(key + "\n"); err != nil {
			return fmt.Errorf("error writing key %s to file: %w", value, err)
		}
		pubkeys = append(pubkeys, value)
	}
	km.updatePublicKeys(pubkeys)
	return nil
}

func (km *Keymanager) arePublicKeysEmpty() bool {
	km.lock.RLock()
	defer km.lock.RUnlock()
	return len(km.providedPublicKeys) == 0
}

func (km *Keymanager) refreshRemoteKeysFromFileChanges(ctx context.Context) error {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return errors.Wrap(err, "could not initialize file watcher")
	}
	defer func() {
		if err := watcher.Close(); err != nil {
			log.WithError(err).Error("Could not close file watcher")
		}
	}()
	initialFileInfo, err := os.Stat(km.keyFilePath)
	if err != nil {
		return errors.Wrap(err, "could not stat remote signer public key file")
	}
	initialFileSize := initialFileInfo.Size()
	if err := watcher.Add(km.keyFilePath); err != nil {
		return errors.Wrap(err, "could not add file to file watcher")
	}
	log.WithField("path", km.keyFilePath).Info("Successfully initialized file watcher")
	km.retriesRemaining = maxRetries // reset retries to default
	// reinitialize keys if watcher reinitialized
	if km.arePublicKeysEmpty() {
		_, fk, err := km.readKeyFile()
		if err != nil {
			return errors.Wrap(err, "could not read key file")
		}
		maps.Copy(fk, km.flagLoadedKeysMap)
		if err = km.savePublicKeysToFile(fk); err != nil {
			return errors.Wrap(err, "could not save public keys to file")
		}
		km.updatePublicKeys(slices.Collect(maps.Values(fk)))
	}
	for {
		select {
		case e, ok := <-watcher.Events:
			if !ok { // Channel was closed (i.e. Watcher.Close() was called).
				log.Info("Closing file watcher")
				return nil
			}
			log.WithFields(logrus.Fields{
				"event": e.Name,
				"op":    e.Op.String(),
			}).Debug("Remote signer key file event triggered")
			if e.Has(fsnotify.Remove) {
				return errors.New("remote signer key file was removed")
			}
			currentFileInfo, err := os.Stat(km.keyFilePath)
			if err != nil {
				return errors.Wrap(err, "could not stat remote signer public key file")
			}
			if currentFileInfo.Size() != initialFileSize {
				log.Info("Remote signer key file updated")
				fileKeys, _, err := km.readKeyFile()
				if err != nil {
					return errors.New("could not read key file")
				}
				// prioritize file keys over flag keys
				if len(fileKeys) == 0 {
					log.Warnln("Remote signer key file no longer has keys, defaulting to flag provided keys")
					fileKeys = slices.Collect(maps.Values(km.flagLoadedKeysMap))
				}
				currentKeys, err := km.FetchValidatingPublicKeys(ctx)
				if err != nil {
					return errors.Wrap(err, "could not fetch current keys")
				}
				if !slices.Equal(currentKeys, fileKeys) {
					km.updatePublicKeys(fileKeys)
				}
				initialFileSize = currentFileInfo.Size()
			}
		case err, ok := <-watcher.Errors:
			if !ok { // Channel was closed (i.e. Watcher.Close() was called).
				log.Info("Closing file watcher")
				return nil
			}
			return errors.Wrap(err, "could not watch for file changes")
		case <-ctx.Done():
			log.Info("Closing file watcher")
			return nil
		}
	}
}

func (km *Keymanager) updatePublicKeys(keys [][48]byte) {
	km.lock.Lock()
	defer km.lock.Unlock()
	km.providedPublicKeys = keys
	km.accountsChangedFeed.Send(keys)
	log.WithField("count", len(km.providedPublicKeys)).Debug("Updated public keys")
}

// FetchValidatingPublicKeys fetches the validating public keys
func (km *Keymanager) FetchValidatingPublicKeys(_ context.Context) ([][fieldparams.BLSPubkeyLength]byte, error) {
	km.lock.RLock()
	defer km.lock.RUnlock()
	log.WithField("count", len(km.providedPublicKeys)).Debug("Fetched validating public keys")
	return km.providedPublicKeys, nil
}

// Sign signs the message by using a remote web3signer server.
func (km *Keymanager) Sign(ctx context.Context, request *validatorpb.SignRequest) (bls.Signature, error) {
	signRequest, err := getSignRequestJson(ctx, km.validator, request, km.genesisValidatorsRoot)
	if err != nil {
		erroredResponsesTotal.Inc()
		return nil, err
	}
	signature, err := km.client.Sign(ctx, hexutil.Encode(request.PublicKey), signRequest)
	if err != nil {
		erroredResponsesTotal.Inc()
		return nil, errors.Wrap(err, "failed to sign the request")
	}
	log.WithField("publicKey", request.PublicKey).Debug("Successfully signed the request")
	signRequestsTotal.Inc()
	return signature, nil
}

// getSignRequestJson returns a json request based on the SignRequest type.
func getSignRequestJson(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) (internal.SignRequestJson, error) {
	if request == nil {
		return nil, errors.New("nil sign request provided")
	}
	if !bytesutil.IsValidRoot(genesisValidatorsRoot) {
		return nil, fmt.Errorf("invalid genesis validators root length, genesis root: %v", genesisValidatorsRoot)
	}
	switch request.Object.(type) {
	case *validatorpb.SignRequest_Block:
		return handleBlock(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_AttestationData:
		return handleAttestationData(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_AggregateAttestationAndProof:
		// TODO: update to V2 sometime after release
		return handleAggregateAttestationAndProof(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_AggregateAttestationAndProofElectra:
		return handleAggregateAttestationAndProofV2(ctx, version.Electra, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_Slot:
		return handleAggregationSlot(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_BlockAltair:
		return handleBlockAltair(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_BlockBellatrix:
		return handleBlockBellatrix(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_BlindedBlockBellatrix:
		return handleBlindedBlockBellatrix(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_BlockCapella:
		return handleBlockCapella(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_BlindedBlockCapella:
		return handleBlindedBlockCapella(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_BlockDeneb:
		return handleBlockDeneb(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_BlindedBlockDeneb:
		return handleBlindedBlockDeneb(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_BlockElectra:
		return handleBlockElectra(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_BlindedBlockElectra:
		return handleBlindedBlockElectra(ctx, validator, request, genesisValidatorsRoot)
	// We do not support "DEPOSIT" type.
	/*
		case *validatorpb.:
		return "DEPOSIT", nil
	*/

	case *validatorpb.SignRequest_Epoch:
		// tech debt that prysm uses signing type epoch
		return handleRandaoReveal(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_Exit:
		return handleVoluntaryExit(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_SyncMessageBlockRoot:
		return handleSyncMessageBlockRoot(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_SyncAggregatorSelectionData:
		return handleSyncAggregatorSelectionData(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_ContributionAndProof:
		return handleContributionAndProof(ctx, validator, request, genesisValidatorsRoot)
	case *validatorpb.SignRequest_Registration:
		return handleRegistration(ctx, validator, request)
	default:
		return nil, fmt.Errorf("web3signer sign request type %T not supported", request.Object)
	}
}

func handleBlock(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	bockSignRequest, err := types.GetBlockSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, bockSignRequest); err != nil {
		return nil, err
	}
	remoteBlockSignRequestsTotal.WithLabelValues("phase0", "false").Inc()
	return json.Marshal(bockSignRequest)
}

func handleAttestationData(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	attestationSignRequest, err := types.GetAttestationSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, attestationSignRequest); err != nil {
		return nil, err
	}
	attestationSignRequestsTotal.Inc()
	return json.Marshal(attestationSignRequest)
}

func handleAggregateAttestationAndProof(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	aggregateAndProofSignRequest, err := types.GetAggregateAndProofSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, aggregateAndProofSignRequest); err != nil {
		return nil, err
	}
	aggregateAndProofSignRequestsTotal.Inc()
	return json.Marshal(aggregateAndProofSignRequest)
}

func handleAggregateAttestationAndProofV2(ctx context.Context, fork int, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	aggregateAndProofSignRequestV2, err := types.GetAggregateAndProofV2SignRequest(fork, request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, aggregateAndProofSignRequestV2); err != nil {
		return nil, err
	}
	aggregateAndProofSignRequestsTotal.Inc()
	return json.Marshal(aggregateAndProofSignRequestV2)
}

func handleAggregationSlot(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	aggregationSlotSignRequest, err := types.GetAggregationSlotSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, aggregationSlotSignRequest); err != nil {
		return nil, err
	}
	aggregationSlotSignRequestsTotal.Inc()
	return json.Marshal(aggregationSlotSignRequest)
}

func handleBlockAltair(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	blockv2AltairSignRequest, err := types.GetBlockAltairSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, blockv2AltairSignRequest); err != nil {
		return nil, err
	}
	remoteBlockSignRequestsTotal.WithLabelValues("altair", "false").Inc()
	return json.Marshal(blockv2AltairSignRequest)
}

func handleBlockBellatrix(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	blockv2BellatrixSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, blockv2BellatrixSignRequest); err != nil {
		return nil, err
	}
	remoteBlockSignRequestsTotal.WithLabelValues("bellatrix", "false").Inc()
	return json.Marshal(blockv2BellatrixSignRequest)
}

func handleBlindedBlockBellatrix(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	blindedBlockv2SignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, blindedBlockv2SignRequest); err != nil {
		return nil, err
	}
	remoteBlockSignRequestsTotal.WithLabelValues("bellatrix", "true").Inc()
	return json.Marshal(blindedBlockv2SignRequest)
}

func handleBlockCapella(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	blockv2CapellaSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, blockv2CapellaSignRequest); err != nil {
		return nil, err
	}
	remoteBlockSignRequestsTotal.WithLabelValues("capella", "false").Inc()
	return json.Marshal(blockv2CapellaSignRequest)
}

func handleBlindedBlockCapella(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	blindedBlockv2CapellaSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, blindedBlockv2CapellaSignRequest); err != nil {
		return nil, err
	}
	remoteBlockSignRequestsTotal.WithLabelValues("capella", "true").Inc()
	return json.Marshal(blindedBlockv2CapellaSignRequest)
}

func handleBlockDeneb(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	blockv2DenebSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, blockv2DenebSignRequest); err != nil {
		return nil, err
	}
	remoteBlockSignRequestsTotal.WithLabelValues("deneb", "false").Inc()
	return json.Marshal(blockv2DenebSignRequest)
}

func handleBlindedBlockDeneb(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	blindedBlockv2DenebSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, blindedBlockv2DenebSignRequest); err != nil {
		return nil, err
	}
	remoteBlockSignRequestsTotal.WithLabelValues("deneb", "true").Inc()
	return json.Marshal(blindedBlockv2DenebSignRequest)
}

func handleBlockElectra(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	blockv2ElectraSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, blockv2ElectraSignRequest); err != nil {
		return nil, err
	}
	remoteBlockSignRequestsTotal.WithLabelValues("electra", "false").Inc()
	return json.Marshal(blockv2ElectraSignRequest)
}

func handleBlindedBlockElectra(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	blindedBlockv2ElectraSignRequest, err := types.GetBlockV2BlindedSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, blindedBlockv2ElectraSignRequest); err != nil {
		return nil, err
	}
	remoteBlockSignRequestsTotal.WithLabelValues("electra", "true").Inc()
	return json.Marshal(blindedBlockv2ElectraSignRequest)
}

func handleRandaoReveal(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	randaoRevealSignRequest, err := types.GetRandaoRevealSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, randaoRevealSignRequest); err != nil {
		return nil, err
	}
	randaoRevealSignRequestsTotal.Inc()
	return json.Marshal(randaoRevealSignRequest)
}

func handleVoluntaryExit(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	voluntaryExitRequest, err := types.GetVoluntaryExitSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, voluntaryExitRequest); err != nil {
		return nil, err
	}
	voluntaryExitSignRequestsTotal.Inc()
	return json.Marshal(voluntaryExitRequest)
}

func handleSyncMessageBlockRoot(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	syncCommitteeMessageRequest, err := types.GetSyncCommitteeMessageSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, syncCommitteeMessageRequest); err != nil {
		return nil, err
	}
	syncCommitteeMessageSignRequestsTotal.Inc()
	return json.Marshal(syncCommitteeMessageRequest)
}

func handleSyncAggregatorSelectionData(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	syncCommitteeSelectionProofRequest, err := types.GetSyncCommitteeSelectionProofSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, syncCommitteeSelectionProofRequest); err != nil {
		return nil, err
	}
	syncCommitteeSelectionProofSignRequestsTotal.Inc()
	return json.Marshal(syncCommitteeSelectionProofRequest)
}

func handleContributionAndProof(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest, genesisValidatorsRoot []byte) ([]byte, error) {
	contributionAndProofRequest, err := types.GetSyncCommitteeContributionAndProofSignRequest(request, genesisValidatorsRoot)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, contributionAndProofRequest); err != nil {
		return nil, err
	}
	syncCommitteeContributionAndProofSignRequestsTotal.Inc()
	return json.Marshal(contributionAndProofRequest)
}

func handleRegistration(ctx context.Context, validator *validator.Validate, request *validatorpb.SignRequest) ([]byte, error) {
	validatorRegistrationRequest, err := types.GetValidatorRegistrationSignRequest(request)
	if err != nil {
		return nil, err
	}
	if err = validator.StructCtx(ctx, validatorRegistrationRequest); err != nil {
		return nil, err
	}
	validatorRegistrationSignRequestsTotal.Inc()
	return json.Marshal(validatorRegistrationRequest)
}

// SubscribeAccountChanges returns the event subscription for changes to public keys.
func (km *Keymanager) SubscribeAccountChanges(pubKeysChan chan [][fieldparams.BLSPubkeyLength]byte) event.Subscription {
	return km.accountsChangedFeed.Subscribe(pubKeysChan)
}

// ExtractKeystores is not supported for the remote-web3signer keymanager type.
func (*Keymanager) ExtractKeystores(
	_ context.Context, _ []bls.PublicKey, _ string,
) ([]*keymanager.Keystore, error) {
	return nil, errors.New("extracting keys is not supported for a web3signer keymanager")
}

// DeleteKeystores is not supported for the remote-web3signer keymanager type.
func (km *Keymanager) DeleteKeystores(context.Context, [][]byte) ([]*keymanager.KeyStatus, error) {
	return nil, errors.New("Wrong wallet type: web3-signer. Only Imported or Derived wallets can delete accounts")
}

func (km *Keymanager) ListKeymanagerAccounts(ctx context.Context, cfg keymanager.ListKeymanagerAccountConfig) error {
	au := aurora.NewAurora(true)
	fmt.Printf("(keymanager kind) %s\n", au.BrightGreen("web3signer").Bold())
	fmt.Printf(
		"(configuration file path) %s\n",
		au.BrightGreen(filepath.Join(cfg.WalletAccountsDir, cfg.KeymanagerConfigFileName)).Bold(),
	)
	fmt.Println(" ")
	fmt.Printf("%s\n", au.BrightGreen("Setup Configuration").Bold())
	fmt.Println(" ")
	//TODO: add config options, may require refactor again
	validatingPubKeys, err := km.FetchValidatingPublicKeys(ctx)
	if err != nil {
		return errors.Wrap(err, "could not fetch validating public keys")
	}
	if len(validatingPubKeys) == 1 {
		fmt.Print("Showing 1 validator account\n")
	} else if len(validatingPubKeys) == 0 {
		fmt.Print("No accounts found\n")
		return nil
	} else {
		fmt.Printf("Showing %d validator accounts\n", len(validatingPubKeys))
	}
	DisplayRemotePublicKeys(validatingPubKeys)
	return nil
}

// DisplayRemotePublicKeys prints remote public keys to stdout.
func DisplayRemotePublicKeys(validatingPubKeys [][48]byte) {
	au := aurora.NewAurora(true)
	for i := 0; i < len(validatingPubKeys); i++ {
		fmt.Println("")
		fmt.Printf(
			"%s\n", au.BrightGreen(petnames.DeterministicName(validatingPubKeys[i][:], "-")).Bold(),
		)
		// Retrieve the validating key account metadata.
		fmt.Printf("%s %#x\n", au.BrightCyan("[validating public key]").Bold(), validatingPubKeys[i])
		fmt.Println(" ")
	}
}

// AddPublicKeys imports a list of public keys into the keymanager for web3signer use. Returns status with message.
func (km *Keymanager) AddPublicKeys(pubKeys []string) ([]*keymanager.KeyStatus, error) {
	importedRemoteKeysStatuses := make([]*keymanager.KeyStatus, len(pubKeys))
	// Using a map to track both existing and new public keys efficiently
	combinedKeys := make(map[string][48]byte)

	// Populate the map with existing keys
	km.lock.RLock()
	originalKeysLen := len(km.providedPublicKeys)
	for _, key := range km.providedPublicKeys {
		encodedKey := hexutil.Encode(key[:])
		combinedKeys[encodedKey] = key
	}
	km.lock.RUnlock()

	for i, pubkey := range pubKeys {
		pubkeyBytes, err := hexutil.Decode(pubkey)
		if err != nil {
			importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
				Status:  keymanager.StatusError,
				Message: err.Error(),
			}
			continue
		}
		if len(pubkeyBytes) != fieldparams.BLSPubkeyLength {
			importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
				Status:  keymanager.StatusError,
				Message: fmt.Sprintf("pubkey byte length (%d) did not match bls pubkey byte length (%d)", len(pubkeyBytes), fieldparams.BLSPubkeyLength),
			}
			continue
		}

		encodedPubkey := hexutil.Encode(pubkeyBytes)
		if _, exists := combinedKeys[encodedPubkey]; exists {
			importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
				Status:  keymanager.StatusDuplicate,
				Message: fmt.Sprintf("Duplicate pubkey: %v, already in use", pubkey),
			}
			continue
		}

		// Add the new key to the map
		combinedKeys[encodedPubkey] = bytesutil.ToBytes48(pubkeyBytes)
		importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
			Status:  keymanager.StatusImported,
			Message: fmt.Sprintf("Successfully added pubkey: %v", pubkey),
		}
		log.Debug("Added pubkey to keymanager for web3signer", "pubkey", pubkey)
	}

	if originalKeysLen != len(combinedKeys) {
		if km.keyFilePath != "" {
			if err := km.savePublicKeysToFile(combinedKeys); err != nil {
				return nil, err
			}
		} else {
			km.updatePublicKeys(slices.Collect(maps.Values(combinedKeys)))
		}
	}

	return importedRemoteKeysStatuses, nil
}

// DeletePublicKeys removes a list of public keys from the keymanager for web3signer use. Returns status with message.
func (km *Keymanager) DeletePublicKeys(publicKeys []string) ([]*keymanager.KeyStatus, error) {
	deletedRemoteKeysStatuses := make([]*keymanager.KeyStatus, len(publicKeys))
	// Using a map to track both existing and new public keys efficiently
	combinedKeys := make(map[string][48]byte)
	km.lock.RLock()
	originalKeysLen := len(km.providedPublicKeys)
	if originalKeysLen == 0 {
		for i := range deletedRemoteKeysStatuses {
			deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
				Status:  keymanager.StatusNotFound,
				Message: "No pubkeys are set in validator",
			}
		}
		return deletedRemoteKeysStatuses, nil
	}

	// Populate the map with existing keys
	for _, key := range km.providedPublicKeys {
		encodedKey := hexutil.Encode(key[:])
		combinedKeys[encodedKey] = key
	}
	km.lock.RUnlock()

	for i, pubkey := range publicKeys {
		pubkeyBytes, err := hexutil.Decode(pubkey)
		if err != nil {
			deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
				Status:  keymanager.StatusError,
				Message: err.Error(),
			}
			continue
		}
		if len(pubkeyBytes) != fieldparams.BLSPubkeyLength {
			deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
				Status:  keymanager.StatusError,
				Message: fmt.Sprintf("pubkey byte length (%d) did not match bls pubkey byte length (%d)", len(pubkeyBytes), fieldparams.BLSPubkeyLength),
			}
			continue
		}
		_, exists := combinedKeys[pubkey]
		if !exists {
			deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
				Status:  keymanager.StatusNotFound,
				Message: fmt.Sprintf("Pubkey: %v not found", pubkey),
			}
			continue
		}
		delete(combinedKeys, pubkey)
		deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
			Status:  keymanager.StatusDeleted,
			Message: fmt.Sprintf("Successfully deleted pubkey: %v", pubkey),
		}
		log.WithField("pubkey", pubkey).Debug("Deleted pubkey from keymanager for remote signer")
	}

	if originalKeysLen != len(combinedKeys) {
		if km.keyFilePath != "" {
			if err := km.savePublicKeysToFile(combinedKeys); err != nil {
				return nil, err
			}
		} else {
			km.updatePublicKeys(slices.Collect(maps.Values(combinedKeys)))
		}
	}

	return deletedRemoteKeysStatuses, nil
}
