package validator

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/OffchainLabs/prysm/v7/api/client"
	"github.com/OffchainLabs/prysm/v7/validator/rpc"
	"github.com/pkg/errors"
)

const (
	localKeysPath    = "/eth/v1/keystores"
	remoteKeysPath   = "/eth/v1/remotekeys"
	feeRecipientPath = "/eth/v1/validator/{pubkey}/feerecipient"
)

// Client provides a collection of helper methods for calling the Keymanager API endpoints.
type Client struct {
	*client.Client
}

// NewClient returns a new Client that includes functions for REST calls to keymanager APIs.
func NewClient(host string, opts ...client.ClientOpt) (*Client, error) {
	c, err := client.NewClient(host, opts...)
	if err != nil {
		return nil, err
	}
	return &Client{c}, nil
}

// GetValidatorPubKeys gets the current list of web3signer or the local validator public keys in hex format.
func (c *Client) GetValidatorPubKeys(ctx context.Context) ([]string, error) {
	jsonlocal, err := c.GetLocalValidatorKeys(ctx)
	if err != nil {
		return nil, err
	}
	jsonremote, err := c.GetRemoteValidatorKeys(ctx)
	if err != nil {
		return nil, err
	}
	if len(jsonlocal.Data) == 0 && len(jsonremote.Data) == 0 {
		return nil, errors.New("there are no local keys or remote keys on the validator")
	}

	hexKeys := make(map[string]bool)

	for index := range jsonlocal.Data {
		hexKeys[jsonlocal.Data[index].ValidatingPubkey] = true
	}
	for index := range jsonremote.Data {
		hexKeys[jsonremote.Data[index].Pubkey] = true
	}
	keys := make([]string, 0)
	for k := range hexKeys {
		keys = append(keys, k)
	}
	return keys, nil
}

// GetLocalValidatorKeys calls the keymanager APIs for local validator keys
func (c *Client) GetLocalValidatorKeys(ctx context.Context) (*rpc.ListKeystoresResponse, error) {
	localBytes, err := c.Get(ctx, localKeysPath, client.WithAuthorizationToken(c.Token()))
	if err != nil {
		return nil, err
	}
	jsonlocal := &rpc.ListKeystoresResponse{}
	if err := json.Unmarshal(localBytes, jsonlocal); err != nil {
		return nil, errors.Wrap(err, "failed to parse local keystore list")
	}
	return jsonlocal, nil
}

// GetRemoteValidatorKeys calls the keymanager APIs for web3signer validator keys
func (c *Client) GetRemoteValidatorKeys(ctx context.Context) (*rpc.ListRemoteKeysResponse, error) {
	remoteBytes, err := c.Get(ctx, remoteKeysPath, client.WithAuthorizationToken(c.Token()))
	if err != nil {
		if !strings.Contains(err.Error(), "Prysm Wallet is not of type Web3Signer") {
			return nil, err
		}
	}
	jsonremote := &rpc.ListRemoteKeysResponse{}
	if len(remoteBytes) != 0 {
		if err := json.Unmarshal(remoteBytes, jsonremote); err != nil {
			return nil, errors.Wrap(err, "failed to parse remote keystore list")
		}
	}
	return jsonremote, nil
}

// GetFeeRecipientAddresses takes a list of validators in hex format and returns an equal length list of fee recipients in hex format.
func (c *Client) GetFeeRecipientAddresses(ctx context.Context, validators []string) ([]string, error) {
	feeRecipients := make([]string, len(validators))
	for index, validator := range validators {
		feejson, err := c.GetFeeRecipientAddress(ctx, validator)
		if err != nil {
			return nil, errors.Wrap(err, fmt.Sprintf("keymanager API failed to retrieve fee recipient for validator %s", validators[index]))
		}
		if feejson.Data == nil {
			continue
		}
		feeRecipients[index] = feejson.Data.Ethaddress
	}
	return feeRecipients, nil
}

// GetFeeRecipientAddress takes a public key and calls the keymanager API to return its fee recipient.
func (c *Client) GetFeeRecipientAddress(ctx context.Context, pubkey string) (*rpc.GetFeeRecipientByPubkeyResponse, error) {
	path := strings.Replace(feeRecipientPath, "{pubkey}", pubkey, 1)
	b, err := c.Get(ctx, path, client.WithAuthorizationToken(c.Token()))
	if err != nil {
		return nil, err
	}
	feejson := &rpc.GetFeeRecipientByPubkeyResponse{}
	if err := json.Unmarshal(b, feejson); err != nil {
		return nil, errors.Wrap(err, "failed to parse fee recipient")
	}
	return feejson, nil
}
