// Package rpc implements the Bitcoin JSON-RPC API service for Teranode.
//
// The rpc package provides a full-featured and standards-compliant Bitcoin JSON-RPC server
// implementation that enables external clients to interact with the Teranode node
// using established protocols. The service implements both standard Bitcoin Core RPC methods
// and Bitcoin SV specific extensions, supporting operations such as:
//
// - Blockchain data retrieval (blocks, transactions, chain state)
// - Transaction submission and broadcast
// - Mining interfaces (block templates, mining solutions)
// - Network peer management
// - Node configuration and status information
//
// Architecture:
// The RPC service is architected as a concurrent HTTP server that processes JSON-RPC
// formatted requests from authenticated clients. Key components include:
//
// - RPCServer: The core server implementation handling connections, authentication, and request routing
// - Handlers: Individual command processors for each supported RPC method
// - Distributor: Component for reliable transaction propagation to the network
// - Authentication: Two-tier system with admin and limited-access users
//
// Security features:
// - HTTP Basic authentication with SHA256 credential validation
// - Connection limiting to prevent denial-of-service attacks
// - Configurable access levels for sensitive operations
// - Proper HTTP request/response handling with appropriate headers
//
// Integration with other Teranode services:
// The RPC service integrates with several other Teranode components including:
// - Blockchain service: For block data and chain state information
// - Block Assembly service: For mining operations
// - P2P service: For peer information and management
// - Legacy service: For compatibility with older network protocols
// - UTXO store: For transaction validation
//
// Performance considerations:
// - Connection pooling and limits to prevent resource exhaustion
// - Response caching for frequently accessed data
// - Timeout handling to prevent client connections from blocking indefinitely
// - Graceful shutdown to complete in-progress requests
package rpc

import (
	"context"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

	"github.com/bsv-blockchain/teranode/errors"
	"github.com/bsv-blockchain/teranode/services/blockassembly"
	"github.com/bsv-blockchain/teranode/services/blockchain"
	"github.com/bsv-blockchain/teranode/services/blockvalidation"
	"github.com/bsv-blockchain/teranode/services/legacy/peer"
	"github.com/bsv-blockchain/teranode/services/p2p"
	"github.com/bsv-blockchain/teranode/services/rpc/bsvjson"
	"github.com/bsv-blockchain/teranode/settings"
	"github.com/bsv-blockchain/teranode/stores/utxo"
	"github.com/bsv-blockchain/teranode/ulogger"
	"github.com/bsv-blockchain/teranode/util"
	"github.com/bsv-blockchain/teranode/util/health"
	"github.com/ordishs/gocore"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/propagation"
)

// API version constants
const (
	jsonrpcSemverString = "1.3.0"
	jsonrpcSemverMajor  = 1
	jsonrpcSemverMinor  = 3
	jsonrpcSemverPatch  = 0
)

const (
	// rpcAuthTimeoutSeconds is the number of seconds a connection to the
	// RPC server is allowed to stay open without authenticating before it
	// is closed.
	rpcAuthTimeoutSeconds = 10

	// uint256Size is the number of bytes needed to represent an unsigned
	// 256-bit integer.
	// uint256Size = 32

	// gbtNonceRange is two 32-bit big-endian hexadecimal integers which
	// represent the valid ranges of nonces returned by the getblocktemplate
	// RPC.
	// gbtNonceRange = "00000000ffffffff"

	// gbtRegenerateSeconds is the number of seconds that must pass before
	// a new template is generated when the previous block hash has not
	// changed and there have been changes to the available transactions
	// in the memory pool.
	// gbtRegenerateSeconds = 60

	// maxProtocolVersion is the max protocol version the server supports.
	// maxProtocolVersion = wire.FeeFilterVersion
)

// RPCStat provides performance statistics collection for the RPC service.
var RPCStat = gocore.NewStat("RPC")

var (
// gbtMutableFields are the manipulations the server allows to be made
// to block templates generated by the getblocktemplate RPC.  It is
// declared here to avoid the overhead of creating the slice on every
// invocation for constant data.
// gbtMutableFields = []string{
// 	"time", "transactions/add", "prevblock", "coinbase/append",
// }

// gbtCapabilities describes additional capabilities returned with a
// block template generated by the getblocktemplate RPC.    It is
// declared here to avoid the overhead of creating the slice on every
// invocation for constant data.
// gbtCapabilities = []string{"proposal"}
)

// Errors
var (
	// ErrRPCUnimplemented is an error returned to RPC clients when the
	// provided command is recognized, but not implemented.
	ErrRPCUnimplemented = &bsvjson.RPCError{
		Code:    bsvjson.ErrRPCUnimplemented,
		Message: "Command unimplemented",
	}

	// ErrRPCNoWallet is an error returned to RPC clients when the provided
	// command is recognized as a wallet command.
	ErrRPCNoWallet = &bsvjson.RPCError{
		Code:    bsvjson.ErrRPCNoWallet,
		Message: "This implementation does not implement wallet commands",
	}
)

type commandHandler func(context.Context, *RPCServer, interface{}, <-chan struct{}) (interface{}, error)

// rpcHandlers maps RPC command strings to appropriate handler functions.
// This is set by init because help references rpcHandlers and thus causes
// a dependency loop.
var rpcHandlers map[string]commandHandler
var rpcHandlersBeforeInit = map[string]commandHandler{
	"addnode":               handleUnimplemented,
	"createrawtransaction":  handleCreateRawTransaction,
	"debuglevel":            handleUnimplemented,
	"decoderawtransaction":  handleUnimplemented,
	"decodescript":          handleUnimplemented,
	"estimatefee":           handleUnimplemented,
	"generate":              handleGenerate,
	"generatetoaddress":     handleGenerateToAddress,
	"getaddednodeinfo":      handleUnimplemented,
	"getbestblock":          handleUnimplemented,
	"getbestblockhash":      handleGetBestBlockHash,
	"getblock":              handleGetBlock,
	"getblockbyheight":      handleGetBlockByHeight,
	"getblockchaininfo":     handleGetblockchaininfo,
	"getblockcount":         handleUnimplemented,
	"getblockhash":          handleGetBlockHash,
	"getblockheader":        handleGetBlockHeader,
	"getblocktemplate":      handleUnimplemented,
	"getcfilter":            handleUnimplemented,
	"getcfilterheader":      handleUnimplemented,
	"getchaintips":          handleGetchaintips,
	"getconnectioncount":    handleUnimplemented,
	"getcurrentnet":         handleUnimplemented,
	"getdifficulty":         handleGetDifficulty,
	"getgenerate":           handleUnimplemented,
	"gethashespersec":       handleUnimplemented,
	"getheaders":            handleUnimplemented,
	"getinfo":               handleGetInfo,
	"getmempoolinfo":        handleUnimplemented,
	"getmininginfo":         handleGetMiningInfo,
	"getnettotals":          handleUnimplemented,
	"getnetworkhashps":      handleUnimplemented,
	"getpeerinfo":           handleGetpeerinfo,
	"getrawmempool":         handleGetRawMempool,
	"getrawtransaction":     handleGetRawTransaction,
	"gettxout":              handleUnimplemented,
	"gettxoutproof":         handleUnimplemented,
	"help":                  handleHelp,
	"node":                  handleUnimplemented,
	"ping":                  handleUnimplemented,
	"invalidateblock":       handleInvalidateBlock,
	"isbanned":              handleIsBanned,
	"listbanned":            handleListBanned,
	"clearbanned":           handleClearBanned,
	"reconsiderblock":       handleReconsiderBlock,
	"searchrawtransactions": handleUnimplemented,
	"sendrawtransaction":    handleSendRawTransaction,
	"setban":                handleSetBan,
	"setgenerate":           handleUnimplemented,
	"stop":                  handleStop,
	"submitblock":           handleUnimplemented,
	"uptime":                handleUnimplemented,
	"validateaddress":       handleUnimplemented,
	"verifychain":           handleUnimplemented,
	"verifymessage":         handleUnimplemented,
	"verifytxoutproof":      handleUnimplemented,
	"version":               handleVersion,
	// BSV mining methods
	"getminingcandidate":   handleGetMiningCandidate,
	"submitminingsolution": handleSubmitMiningSolution,

	// BSV alert methods
	"freeze":   handleFreeze,
	"unfreeze": handleUnfreeze,
	"reassign": handleReassign,
}

// list of commands that we recognize, but for which bsvd has no support because
// it lacks support for wallet functionality. For these commands the user
// should ask a connected instance of bsvwallet.
var rpcAskWallet = map[string]struct{}{
	"addmultisigaddress":     {},
	"backupwallet":           {},
	"createencryptedwallet":  {},
	"createmultisig":         {},
	"dumpprivkey":            {},
	"dumpwallet":             {},
	"encryptwallet":          {},
	"getaccount":             {},
	"getaccountaddress":      {},
	"getaddressesbyaccount":  {},
	"getbalance":             {},
	"getnewaddress":          {},
	"getrawchangeaddress":    {},
	"getreceivedbyaccount":   {},
	"getreceivedbyaddress":   {},
	"gettransaction":         {},
	"gettxoutsetinfo":        {},
	"getunconfirmedbalance":  {},
	"getwalletinfo":          {},
	"importprivkey":          {},
	"importwallet":           {},
	"keypoolrefill":          {},
	"listaccounts":           {},
	"listaddressgroupings":   {},
	"listlockunspent":        {},
	"listreceivedbyaccount":  {},
	"listreceivedbyaddress":  {},
	"listsinceblock":         {},
	"listtransactions":       {},
	"listunspent":            {},
	"lockunspent":            {},
	"move":                   {},
	"sendfrom":               {},
	"sendmany":               {},
	"sendtoaddress":          {},
	"setaccount":             {},
	"settxfee":               {},
	"signmessage":            {},
	"signrawtransaction":     {},
	"walletlock":             {},
	"walletpassphrase":       {},
	"walletpassphrasechange": {},
}

// Commands that are currently unimplemented, but should ultimately be.
var rpcUnimplemented = map[string]struct{}{
	"estimatepriority": {},
	"getchaintips":     {},
	"getmempoolentry":  {},
	"getnetworkinfo":   {},
	"getwork":          {},
	// "invalidateblock":  {},
	"preciousblock": {},
}

// Commands that are available to a limited user
var rpcLimited = map[string]struct{}{
	// Websockets commands
	"loadtxfilter":          {},
	"notifyblocks":          {},
	"notifynewtransactions": {},
	"notifyreceived":        {},
	"notifyspent":           {},
	"rescan":                {},
	"rescanblocks":          {},
	"session":               {},

	// Websockets AND HTTP/S commands
	"help": {},

	// HTTP/S-only commands
	"createrawtransaction":  {},
	"decoderawtransaction":  {},
	"decodescript":          {},
	"estimatefee":           {},
	"getbestblock":          {},
	"getbestblockhash":      {},
	"getblock":              {},
	"getblockcount":         {},
	"getblockhash":          {},
	"getblockheader":        {},
	"getcfilter":            {},
	"getcfilterheader":      {},
	"getcurrentnet":         {},
	"getdifficulty":         {},
	"getheaders":            {},
	"getinfo":               {},
	"getnettotals":          {},
	"getnetworkhashps":      {},
	"getrawmempool":         {},
	"getrawtransaction":     {},
	"gettxout":              {},
	"gettxoutproof":         {},
	"searchrawtransactions": {},
	"sendrawtransaction":    {},
	"submitblock":           {},
	"uptime":                {},
	"validateaddress":       {},
	"verifymessage":         {},
	"verifytxoutproof":      {},
	"version":               {},
	"getminingcandidate":    {},
	"submitminingsolution":  {},
}

// builderScript is a convenience function which is used for hard-coded scripts
// built with the script builder.   Any errors are converted to a panic since it
// is only, and must only, be used with hard-coded, and therefore, known good,
// scripts.
// func builderScript(builder *txscript.ScriptBuilder) []byte {
// 	script, err := builder.Script()
// 	if err != nil {
// 		panic(err)
// 	}
// 	return script
// }

// internalRPCError is a convenience function to convert an internal error to
// an RPC error with the appropriate code set.  It also logs the error to the
// RPC server subsystem since internal errors really should not occur.  The
// context parameter is only used in the log message and may be empty if it's
// not needed.
func (s *RPCServer) internalRPCError(errStr, context string) *bsvjson.RPCError {
	logStr := errStr
	if context != "" {
		logStr = context + ": " + errStr
	}

	s.logger.Errorf("RPC server internal RPC error: %s", logStr)

	return bsvjson.NewRPCError(bsvjson.ErrRPCInternal.Code, errStr)
}

// rpcDecodeHexError is a convenience function for returning a nicely formatted
// RPC error which indicates the provided hex string failed to decode.
func rpcDecodeHexError(gotHex string) *bsvjson.RPCError {
	return bsvjson.NewRPCError(bsvjson.ErrRPCDecodeHexString,
		fmt.Sprintf("Argument must be hexadecimal string (not %q)",
			gotHex))
}

// rpcNoTxInfoError is a convenience function for returning a nicely formatted
// RPC error which indicates there is no information available for the provided
// transaction hash.
// func rpcNoTxInfoError(txHash *chainhash.Hash) *bsvjson.RPCError {
// 	return bsvjson.NewRPCError(bsvjson.ErrRPCNoTxInfo,
// 		fmt.Sprintf("No information available about transaction %v",
// 			txHash))
// }

// handleUnimplemented is the handler for commands that should ultimately be
// supported but are not yet implemented.
func handleUnimplemented(ctx context.Context, s *RPCServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
	s.logger.Debugf("handling unimplemented command")
	return nil, ErrRPCUnimplemented
}

// handleAskWallet is the handler for commands that are recognized as valid, but
// are unable to answer correctly since it involves wallet state.
// These commands will be implemented in bsvwallet.
func handleAskWallet(ctx context.Context, s *RPCServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
	return nil, ErrRPCNoWallet
}

// encodeTemplateID encodes the passed details into an ID that can be used to
// uniquely identify a block template.
// func encodeTemplateID(prevHash *chainhash.Hash, lastGenerated time.Time) string {
// 	return fmt.Sprintf("%s-%d", prevHash.String(), lastGenerated.Unix())
// }

// decodeTemplateID decodes an ID that is used to uniquely identify a block
// template.  This is mainly used as a mechanism to track when to update clients
// that are using long polling for block templates.  The ID consists of the
// previous block hash for the associated template and the time the associated
// template was generated.
// func decodeTemplateID(templateID string) (*chainhash.Hash, int64, error) {
// 	fields := strings.Split(templateID, "-")
// 	if len(fields) != 2 {
// 		return nil, 0, errors.New("invalid longpollid format")
// 	}

// 	prevHash, err := chainhash.NewHashFromStr(fields[0])
// 	if err != nil {
// 		return nil, 0, errors.New("invalid longpollid format")
// 	}
// 	lastGenerated, err := strconv.ParseInt(fields[1], 10, 64)
// 	if err != nil {
// 		return nil, 0, errors.New("invalid longpollid format")
// 	}

// 	return prevHash, lastGenerated, nil
// }

// chainErrToGBTErrString converts an error returned from btcchain to a string
// which matches the reasons and format described in BIP0022 for rejection
// reasons.
// func chainErrToGBTErrString(err error) string {
// 	// When the passed error is not a RuleError, just return a generic
// 	// rejected string with the error text.
// 	ruleErr, ok := err.(bsvd_blockchain.RuleError)
// 	if !ok {
// 		return "rejected: " + err.Error()
// 	}

// 	switch ruleErr.ErrorCode {
// 	case bsvd_blockchain.ErrDuplicateBlock:
// 		return "duplicate"
// 	case bsvd_blockchain.ErrBlockTooBig:
// 		return "bad-blk-length"
// 	case bsvd_blockchain.ErrBlockVersionTooOld:
// 		return "bad-version"
// 	case bsvd_blockchain.ErrInvalidTime:
// 		return "bad-time"
// 	case bsvd_blockchain.ErrTimeTooOld:
// 		return "time-too-old"
// 	case bsvd_blockchain.ErrTimeTooNew:
// 		return "time-too-new"
// 	case bsvd_blockchain.ErrDifficultyTooLow:
// 		return "bad-diffbits"
// 	case bsvd_blockchain.ErrUnexpectedDifficulty:
// 		return "bad-diffbits"
// 	case bsvd_blockchain.ErrHighHash:
// 		return "high-hash"
// 	case bsvd_blockchain.ErrBadMerkleRoot:
// 		return "bad-txnmrklroot"
// 	case bsvd_blockchain.ErrBadCheckpoint:
// 		return "bad-checkpoint"
// 	case bsvd_blockchain.ErrForkTooOld:
// 		return "fork-too-old"
// 	case bsvd_blockchain.ErrCheckpointTimeTooOld:
// 		return "checkpoint-time-too-old"
// 	case bsvd_blockchain.ErrNoTransactions:
// 		return "bad-txns-none"
// 	case bsvd_blockchain.ErrNoTxInputs:
// 		return "bad-txns-noinputs"
// 	case bsvd_blockchain.ErrNoTxOutputs:
// 		return "bad-txns-nooutputs"
// 	case bsvd_blockchain.ErrTxTooBig:
// 		return "bad-txns-size-too-large"
// 	case bsvd_blockchain.ErrTxTooSmall:
// 		return "bad-txns-size-too-small"
// 	case bsvd_blockchain.ErrTxTooManySigOps:
// 		return "bad-txns-too-many-sigops"
// 	case bsvd_blockchain.ErrBadTxOutValue:
// 		return "bad-txns-outputvalue"
// 	case bsvd_blockchain.ErrDuplicateTxInputs:
// 		return "bad-txns-dupinputs"
// 	case bsvd_blockchain.ErrBadTxInput:
// 		return "bad-txns-badinput"
// 	case bsvd_blockchain.ErrMissingTxOut:
// 		return "bad-txns-missinginput"
// 	case bsvd_blockchain.ErrSpentTxOut:
// 		return "bad-txns-spentinput"
// 	case bsvd_blockchain.ErrUnfinalizedTx:
// 		return "bad-txns-unfinalizedtx"
// 	case bsvd_blockchain.ErrDuplicateTx:
// 		return "bad-txns-duplicate"
// 	case bsvd_blockchain.ErrOverwriteTx:
// 		return "bad-txns-overwrite"
// 	case bsvd_blockchain.ErrImmatureSpend:
// 		return "bad-txns-maturity"
// 	case bsvd_blockchain.ErrSpendTooHigh:
// 		return "bad-txns-highspend"
// 	case bsvd_blockchain.ErrBadFees:
// 		return "bad-txns-fees"
// 	case bsvd_blockchain.ErrTooManySigOps:
// 		return "high-sigops"
// 	case bsvd_blockchain.ErrFirstTxNotCoinbase:
// 		return "bad-txns-nocoinbase"
// 	case bsvd_blockchain.ErrMultipleCoinbases:
// 		return "bad-txns-multicoinbase"
// 	case bsvd_blockchain.ErrBadCoinbaseScriptLen:
// 		return "bad-cb-length"
// 	case bsvd_blockchain.ErrBadCoinbaseValue:
// 		return "bad-cb-value"
// 	case bsvd_blockchain.ErrMissingCoinbaseHeight:
// 		return "bad-cb-height"
// 	case bsvd_blockchain.ErrBadCoinbaseHeight:
// 		return "bad-cb-height"
// 	case bsvd_blockchain.ErrScriptMalformed:
// 		return "bad-script-malformed"
// 	case bsvd_blockchain.ErrScriptValidation:
// 		return "bad-script-validate"
// 	case bsvd_blockchain.ErrPreviousBlockUnknown:
// 		return "prev-blk-not-found"
// 	case bsvd_blockchain.ErrInvalidAncestorBlock:
// 		return "bad-prevblk"
// 	case bsvd_blockchain.ErrPrevBlockNotBest:
// 		return "inconclusive-not-best-prvblk"
// 	case bsvd_blockchain.ErrInvalidTxOrder:
// 		return "invalid-transaction-order"
// 	}

// 	return "rejected: " + err.Error()
// }

// handleHelp implements the help command.
// func handleHelp(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
// 	c := cmd.(*bsvjson.HelpCmd)

// 	// Provide a usage overview of all commands when no specific command
// 	// was specified.
// 	var command string
// 	if c.Command != nil {
// 		command = *c.Command
// 	}
// 	if command == "" {
// 		s.logger.Errorf("no command specified")
// 		// usage, err := s.helpCacher.rpcUsage(false)
// 		// 	if err != nil {
// 		// 		context := "Failed to generate RPC usage"
// 		// 		return nil, internalRPCError(err.Error(), context)
// 		// 	}
// 		// return usage, nil
// 	}

// 	// Check that the command asked for is supported and implemented.  Only
// 	// search the main list of handlers since help should not be provided
// 	// for commands that are unimplemented or related to wallet
// 	// functionality.
// 	if _, ok := rpcHandlers[command]; !ok {
// 		return nil, &bsvjson.RPCError{
// 			Code:    bsvjson.ErrRPCInvalidParameter,
// 			Message: "Unknown command: " + command,
// 		}
// 	}

// 	// Get the help for the command.
// 	// help, err := s.helpCacher.rpcMethodHelp(command)
// 	// if err != nil {
// 	// 	context := "Failed to generate help"
// 	// 	return nil, internalRPCError(err.Error(), context)
// 	// }
// 	return nil, nil
// }

// handleStop implements the stop command.
func handleStop(_ context.Context, s *RPCServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
	select {
	case s.requestProcessShutdown <- struct{}{}:
	default:
	}

	return "bsvd stopping.", nil
}

// handleVersion implements the version command.
//
// NOTE: This is a btcsuite extension ported from github.com/decred/dcrd.
func handleVersion(_ context.Context, s *RPCServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
	result := map[string]bsvjson.VersionResult{
		"btcdjsonrpcapi": {
			VersionString: jsonrpcSemverString,
			Major:         jsonrpcSemverMajor,
			Minor:         jsonrpcSemverMinor,
			Patch:         jsonrpcSemverPatch,
		},
	}

	return result, nil
}

// RPCServer provides a concurrent-safe JSON-RPC server implementation for the Bitcoin protocol.
// It handles client authentication, request processing, response generation, and
// maintains connections to other Teranode services to fulfill RPC requests.
//
// The server implements a two-tier authentication system that separates administrative
// capabilities from limited-user operations, providing security through proper authorization.
// It supports standard Bitcoin Core RPC methods and Bitcoin SV extensions for
// compatibility with existing tools while enhancing functionality.
//
// The RPCServer is designed for concurrent operation, employing synchronization mechanisms
// to handle multiple simultaneous client connections without race conditions or resource
// conflicts. It implements proper connection management, graceful shutdown, and health monitoring.
type RPCServer struct {
	// settings contains the configuration parameters for the RPC server including
	// authentication credentials, network binding options, and service parameters
	settings *settings.Settings

	// started indicates whether the server has been started (1) or not (0)
	// This uses atomic operations for thread-safe access
	started int32

	// shutdown indicates whether the server is in the process of shutting down (1) or not (0)
	// This uses atomic operations for thread-safe access
	shutdown int32

	// authsha contains the SHA256 hash of the HTTP basic auth string for admin-level access
	// This is used for authenticating clients with full administrative privileges
	authsha [sha256.Size]byte

	// limitauthsha contains the SHA256 hash of the HTTP basic auth string for limited-level access
	// This is used for authenticating clients with restricted access (read-only operations)
	limitauthsha [sha256.Size]byte

	// numClients tracks the number of connected RPC clients for connection limiting
	// This uses atomic operations for thread-safe access
	numClients int32

	// statusLines maps HTTP status codes to their corresponding status text lines
	// Used for proper HTTP response generation
	statusLines map[int]string

	// statusLock protects concurrent access to the status lines map
	statusLock sync.RWMutex

	// wg is used to wait for all goroutines to exit during shutdown
	wg sync.WaitGroup

	// requestProcessShutdown is closed when an authorized RPC client requests a shutdown
	// This channel is used to notify the main process that a shutdown has been requested
	requestProcessShutdown chan struct{}

	// quit is used to signal the server to shut down
	// All long-running goroutines should monitor this channel for termination signals
	quit chan int

	// logger provides structured logging capabilities for operational and debugging messages
	logger ulogger.Logger

	// rpcMaxClients is the maximum number of concurrent RPC clients allowed
	// This setting helps prevent resource exhaustion from too many simultaneous connections
	rpcMaxClients int

	// rpcQuirks enables backwards-compatible quirks in the RPC server when true
	// This improves compatibility with clients expecting legacy Bitcoin Core behavior
	rpcQuirks bool

	// listeners contains the network listeners for the RPC server
	// Multiple listeners may be active for different IP addresses and ports
	listeners []net.Listener

	// blockchainClient provides access to blockchain data and operations
	// Used for retrieving block information, chain state, and blockchain operations
	blockchainClient blockchain.ClientI

	// blockValidationClient provides access to block validation services
	// Used for validating blocks and triggering revalidation of invalid blocks
	blockValidationClient blockvalidation.Interface

	// blockAssemblyClient provides access to block assembly and mining services
	// Used for mining-related RPC commands like getminingcandidate and generate
	blockAssemblyClient blockassembly.ClientI

	// peerClient provides access to legacy peer network services
	// Used for peer management and information retrieval
	peerClient peer.ClientI

	// p2pClient provides access to the P2P network services
	// Used for modern peer management and network operations
	p2pClient p2p.ClientI

	// assetHTTPURL is the URL where assets (e.g., for HTTP UI) are served from
	assetHTTPURL *url.URL

	// helpCacher caches help text for RPC commands to improve performance
	// Prevents regenerating help text for each request
	helpCacher *helpCacher

	// utxoStore provides access to the UTXO (Unspent Transaction Output) database
	// Used for transaction validation and UTXO queries
	utxoStore utxo.Store
}

// httpStatusLine returns a response Status-Line (RFC 2616 Section 6.1)
// for the given request and response status code.  This function was lifted and
// adapted from the standard library HTTP server code since it's not exported.
func (s *RPCServer) httpStatusLine(req *http.Request, code int) string {
	// Fast path:
	key := code
	proto11 := req.ProtoAtLeast(1, 1)

	if !proto11 {
		key = -key
	}

	s.statusLock.RLock()
	line, ok := s.statusLines[key]
	s.statusLock.RUnlock()

	if ok {
		return line
	}

	// Slow path:
	proto := "HTTP/1.0"
	if proto11 {
		proto = "HTTP/1.1"
	}

	codeStr := strconv.Itoa(code)
	text := http.StatusText(code)

	if text != "" {
		line = proto + " " + codeStr + " " + text + "\r\n"

		s.statusLock.Lock()
		s.statusLines[key] = line
		s.statusLock.Unlock()
	} else {
		text = "status code " + codeStr
		line = proto + " " + codeStr + " " + text + "\r\n"
	}

	return line
}

// writeHTTPResponseHeaders writes the necessary response headers prior to
// writing an HTTP body given a request to use for protocol negotiation, headers
// to write, a status code, and a writer.
func (s *RPCServer) writeHTTPResponseHeaders(req *http.Request, headers http.Header, code int, w io.Writer) error {
	_, err := io.WriteString(w, s.httpStatusLine(req, code))
	if err != nil {
		return err
	}

	err = headers.Write(w)
	if err != nil {
		return err
	}

	_, err = io.WriteString(w, "\r\n")

	return err
}

// Stop gracefully shuts down the RPC server and its associated resources.
// It ensures that any in-progress requests are allowed to complete before terminating,
// and that all network connections are properly closed.
//
// This method implements a thread-safe shutdown mechanism using atomic operations
// to prevent multiple concurrent shutdown attempts. When called, it closes the quit
// channel to signal all goroutines to terminate, then waits for them to exit using
// the wait group before returning.
//
// Parameters:
//   - ctx: Context for cancellation and tracing purposes
//
// Returns:
//   - error: Any error encountered during the shutdown process, or nil if successful
func (s *RPCServer) Stop(ctx context.Context) error {
	if atomic.AddInt32(&s.shutdown, 1) != 1 {
		s.logger.Infof("RPC server is already in the process of shutting down")
		return nil
	}

	s.logger.Warnf("RPC server shutting down")

	close(s.quit)
	s.wg.Wait()
	s.logger.Infof("RPC server shutdown complete")

	return nil
}

// RequestedProcessShutdown returns a channel that is sent to when an authorized
// RPC client requests the process to shutdown via the 'stop' RPC command.
//
// This method provides a mechanism for external clients with administrative privileges
// to trigger a graceful shutdown of the entire Teranode process. The returned channel
// is closed when an authorized shutdown request is received, allowing the main process
// to detect this event and initiate orderly termination of all services.
//
// If the request cannot be read immediately by the main process, it is dropped
// to prevent the RPC server from blocking. This non-blocking behavior ensures
// that the RPC server continues processing other requests even if the shutdown
// signal is not immediately consumed.
//
// Returns:
//   - <-chan struct{}: A receive-only channel that will be closed when a shutdown is requested
func (s *RPCServer) RequestedProcessShutdown() <-chan struct{} {
	return s.requestProcessShutdown
}

// limitConnections implements connection throttling to protect the server from resource exhaustion.
// It responds with an HTTP 503 Service Unavailable status and returns true if adding another
// client would exceed the maximum allowed number of RPC clients (s.rpcMaxClients).
//
// This method is part of the RPC server's defensive programming pattern to maintain
// stability under high load or potential denial-of-service conditions. When the
// connection limit is reached, new connection attempts are rejected with an appropriate
// HTTP error rather than queuing them, which could lead to resource exhaustion.
//
// Parameters:
//   - w: HTTP response writer used to send the 503 error if limits are exceeded
//   - remoteAddr: The IP address and port of the client attempting to connect (for logging)
//
// Returns:
//   - bool: true if connection limits are exceeded and the client was rejected,
//     false if the connection can proceed
//
// This function is safe for concurrent access through the use of atomic operations.
func (s *RPCServer) limitConnections(w http.ResponseWriter, remoteAddr string) bool {
	if int(atomic.LoadInt32(&s.numClients)+1) > s.rpcMaxClients {
		s.logger.Infof("Max RPC clients exceeded [%d] - "+
			"disconnecting client %s", s.rpcMaxClients,
			remoteAddr)
		http.Error(w, "503 Too busy.  Try again later.",
			http.StatusServiceUnavailable)

		return true
	}

	return false
}

// incrementClients atomically increments the count of connected RPC clients.
//
// This method is part of the connection tracking system that enables the server
// to enforce limits on the number of simultaneous connections. It maintains an
// accurate count of standard HTTP JSON-RPC clients currently connected to the server.
// Note that WebSocket clients are tracked separately with their own limits.
//
// The counter is implemented using atomic operations to ensure thread-safety
// in the highly concurrent environment of the RPC server. This guarantees that
// connection counting is accurate even when multiple clients connect or disconnect
// simultaneously.
//
// This function is safe for concurrent access.
func (s *RPCServer) incrementClients() {
	atomic.AddInt32(&s.numClients, 1)
}

// decrementClients atomically decrements the count of connected RPC clients.
//
// This method is called when an RPC client disconnects, properly reducing the
// tracked count of active connections. It works in conjunction with incrementClients
// to maintain an accurate count of standard HTTP JSON-RPC clients currently
// connected to the server. Note that WebSocket clients are tracked separately
// with their own connection limits.
//
// The counter is implemented using atomic operations to ensure thread-safety
// in a concurrent environment. This guarantees that the connection count remains
// accurate even when many clients connect or disconnect simultaneously across
// multiple goroutines.
//
// This function is safe for concurrent access.
func (s *RPCServer) decrementClients() {
	atomic.AddInt32(&s.numClients, -1)
}

// checkAuth implements the two-tier HTTP Basic authentication system for RPC clients.
// It validates credentials supplied in the HTTP request against configured admin and
// limited-access username/password combinations.
//
// The method implements a secure authentication flow that:
//  1. Extracts the Authorization header from the HTTP request
//  2. Validates the credentials against both admin and limited-user authentication strings
//  3. Uses time-constant comparison operations to prevent timing attacks
//  4. Distinguishes between admin users (who can perform state-changing operations)
//     and limited users (who can only perform read-only operations)
//
// Security considerations:
// - Uses SHA256 for credential hashing
// - Implements constant-time comparison to prevent timing attacks
// - Properly handles missing or malformed authentication headers
// - Can be configured to require or not require authentication based on settings
//
// Parameters:
//   - r: The HTTP request containing the Authorization header
//   - require: If true, authentication is mandatory; if false, it's optional
//
// Returns:
//   - bool: Authentication success (true if successful)
//   - bool: Authorization level (true for admin access, false for limited access). The value specifies whether the user can change the state of the node.
//   - error: Authentication error if any occurred, nil on success
//
// The second bool is always false if the first bool is false (authorization
// level is only meaningful for authenticated requests).
func (s *RPCServer) checkAuth(r *http.Request, require bool) (bool, bool, error) {
	authhdr := r.Header["Authorization"]

	if len(authhdr) == 0 {
		if require {
			s.logger.Warnf("RPC authentication failure from %s", r.RemoteAddr)
			return false, false, errors.NewServiceError("auth failure")
		}

		return false, false, nil
	}

	authsha := sha256.Sum256([]byte(authhdr[0]))

	// Check for limited auth first as in environments with limited users, those
	// are probably expected to have a higher volume of calls
	limitcmp := subtle.ConstantTimeCompare(authsha[:], s.limitauthsha[:])
	if limitcmp == 1 {
		return true, false, nil
	}

	// Check for admin-level auth
	cmp := subtle.ConstantTimeCompare(authsha[:], s.authsha[:])
	if cmp == 1 {
		return true, true, nil
	}

	// Request's auth doesn't match either user
	s.logger.Warnf("RPC authentication failure from %s", r.RemoteAddr)

	return false, false, errors.NewServiceError("auth failure")
}

// parsedRPCCmd represents a JSON-RPC request object that has been parsed into
// a known concrete command along with any error that might have happened while
// parsing it.
type parsedRPCCmd struct {
	id     interface{}
	method string
	cmd    interface{}
	err    *bsvjson.RPCError
}

// standardCmdResult checks that a parsed command is a standard Bitcoin JSON-RPC
// command and runs the appropriate handler to reply to the command.  Any
// commands which are not recognized or not implemented will return an error
// suitable for use in replies.
func (s *RPCServer) standardCmdResult(ctx context.Context, cmd *parsedRPCCmd, closeChan <-chan struct{}) (interface{}, error) {
	handler, ok := rpcHandlers[cmd.method]
	if ok {
		goto handled
	}

	_, ok = rpcAskWallet[cmd.method]

	if ok {
		handler = handleAskWallet
		goto handled
	}

	_, ok = rpcUnimplemented[cmd.method]
	if ok {
		handler = handleUnimplemented
		goto handled
	}

	return nil, bsvjson.ErrRPCMethodNotFound
handled:
	// Create a timeout context for the RPC call
	timeoutCtx, cancel := context.WithTimeout(ctx, s.settings.RPC.RPCTimeout)
	defer cancel()

	// Create channels for the result and error
	resultCh := make(chan interface{}, 1)
	errCh := make(chan error, 1)

	// Execute the handler in a goroutine
	go func() {
		result, err := handler(timeoutCtx, s, cmd.cmd, closeChan)
		select {
		case <-timeoutCtx.Done():
			// Context already canceled, do nothing
			return
		default:
			resultCh <- result
			errCh <- err
		}
	}()

	// Wait for either the result, timeout, or close notification
	select {
	case <-timeoutCtx.Done():
		// Check if it was a timeout or context cancellation
		if timeoutCtx.Err() == context.DeadlineExceeded {
			s.logger.Warnf("RPC call '%s' timed out after %v seconds", cmd.method, s.settings.RPC.RPCTimeout.Seconds())
			return nil, &bsvjson.RPCError{
				Code:    bsvjson.ErrRPCTimeout,
				Message: fmt.Sprintf("RPC call timed out after %v seconds", s.settings.RPC.RPCTimeout.Seconds()),
			}
		}
		return nil, ctx.Err()
	case <-closeChan:
		return nil, &bsvjson.RPCError{
			Code:    bsvjson.ErrRPCMisc,
			Message: "Connection closed by client",
		}
	case result := <-resultCh:
		err := <-errCh
		return result, err
	}
}

// parseCmd parses a JSON-RPC request object into known concrete command.  The
// err field of the returned parsedRPCCmd struct will contain an RPC error that
// is suitable for use in replies if the command is invalid in some way such as
// an unregistered command or invalid parameters.
func (s *RPCServer) parseCmd(request *bsvjson.Request) *parsedRPCCmd {
	var parsedCmd parsedRPCCmd
	parsedCmd.id = request.ID
	parsedCmd.method = request.Method

	cmd, err := bsvjson.UnmarshalCmd(request)
	if err != nil {
		s.logger.Errorf("Error unmarshalling command: %v", err)
		// When the error is because the method is not registered,
		// produce a method not found RPC error.
		if jerr, ok := err.(bsvjson.Error); ok &&
			jerr.ErrorCode == bsvjson.ErrUnregisteredMethod {
			parsedCmd.err = bsvjson.ErrRPCMethodNotFound
			return &parsedCmd
		}

		// Otherwise, some type of invalid parameters is the
		// cause, so produce the equivalent RPC error.
		parsedCmd.err = bsvjson.NewRPCError(
			bsvjson.ErrRPCInvalidParams.Code, err.Error())

		return &parsedCmd
	}

	parsedCmd.cmd = cmd

	return &parsedCmd
}

// createMarshalledReply returns a new marshalled JSON-RPC response given the
// passed parameters.  It will automatically convert errors that are not of
// the type *bsvjson.RPCError to the appropriate type as needed.
func (s *RPCServer) createMarshalledReply(id, result interface{}, replyErr error) ([]byte, error) {
	var jsonErr *bsvjson.RPCError

	if replyErr != nil {
		if jErr, ok := replyErr.(*bsvjson.RPCError); ok {
			jsonErr = jErr
		} else {
			jsonErr = s.internalRPCError(replyErr.Error(), "")
		}
	}

	return bsvjson.MarshalResponse(id, result, jsonErr)
}

// jsonRPCRead handles reading and responding to RPC messages.
func (s *RPCServer) jsonRPCRead(w http.ResponseWriter, r *http.Request, isAdmin bool) {
	if atomic.LoadInt32(&s.shutdown) != 0 {
		return
	}

	// Read and close the JSON-RPC request body from the caller.
	body, err := io.ReadAll(r.Body)
	_ = r.Body.Close()

	if err != nil {
		errCode := http.StatusBadRequest
		http.Error(w, fmt.Sprintf("%d error reading JSON message: %v",
			errCode, err), errCode)

		return
	}

	s.logger.Debugf("jsonRPCRead body: %s", body)

	// Unfortunately, the http server doesn't provide the ability to
	// change the read deadline for the new connection and having one breaks
	// long polling.  However, not having a read deadline on the initial
	// connection would mean clients can connect and idle forever.  Thus,
	// hijack the connecton from the HTTP server, clear the read deadline,
	// and handle writing the response manually.
	hj, ok := w.(http.Hijacker)
	if !ok {
		errMsg := "webserver doesn't support hijacking"
		s.logger.Warnf(errMsg)

		errCode := http.StatusInternalServerError
		http.Error(w, strconv.Itoa(errCode)+" "+errMsg, errCode)

		return
	}

	conn, buf, err := hj.Hijack()

	if err != nil {
		s.logger.Warnf("Failed to hijack HTTP connection: %v", err)

		errCode := http.StatusInternalServerError

		http.Error(w, strconv.Itoa(errCode)+" "+err.Error(), errCode)

		return
	}

	defer conn.Close()
	defer buf.Flush()
	// conn.SetReadDeadline(timeZeroVal)

	// Attempt to parse the raw body into a JSON-RPC request.
	var responseID interface{}

	var jsonErr error

	var result interface{}

	var request bsvjson.Request

	if err := json.Unmarshal(body, &request); err != nil {
		jsonErr = &bsvjson.RPCError{
			Code:    bsvjson.ErrRPCParse.Code,
			Message: "Failed to parse request: " + err.Error(),
		}
	}

	if jsonErr == nil {
		// The JSON-RPC 1.0 spec defines that notifications must have their "id"
		// set to null and states that notifications do not have a response.
		//
		// A JSON-RPC 2.0 notification is a request with "json-rpc":"2.0", and
		// without an "id" member. The specification states that notifications
		// must not be responded to. JSON-RPC 2.0 permits the null value as a
		// valid request id, therefore such requests are not notifications.
		//
		// Bitcoin Core serves requests with "id":null or even an absent "id",
		// and responds to such requests with "id":null in the response.
		//
		// Bsvd does not respond to any request without and "id" or "id":null,
		// regardless the indicated JSON-RPC protocol version unless RPC quirks
		// are enabled. With RPC quirks enabled, such requests will be responded
		// to if the reqeust does not indicate JSON-RPC version.
		//
		// RPC quirks can be enabled by the user to avoid compatibility issues
		// with software relying on Core's behavior.
		if request.ID == nil && !(s.rpcQuirks && request.Jsonrpc == "") {
			s.logger.Debugf("request id:%d, rpsQuirks: %t", request.ID, s.rpcQuirks) //

			return
		}

		// The parse was at least successful enough to have an ID so
		// set it for the response.
		responseID = request.ID

		// Setup a close notifier.  Since the connection is hijacked,
		// the CloseNotifer on the ResponseWriter is not available.
		closeChan := make(chan struct{}, 1)

		go func() {
			_, err := conn.Read(make([]byte, 1))
			if err != nil {
				close(closeChan)
			}
		}()

		// Check if the user is limited and set error if method unauthorized
		if !isAdmin {
			if _, ok := rpcLimited[request.Method]; !ok {
				jsonErr = &bsvjson.RPCError{
					Code:    bsvjson.ErrRPCInvalidParams.Code,
					Message: "limited user not authorized for this method",
				}
			}
		}

		if jsonErr == nil {
			// Attempt to parse the JSON-RPC request into a known concrete
			// command.
			parsedCmd := s.parseCmd(&request)
			if parsedCmd.err != nil {
				jsonErr = parsedCmd.err
			} else {
				result, jsonErr = s.standardCmdResult(r.Context(), parsedCmd, closeChan)
			}
		}
	}

	// Marshal the response.
	msg, err := s.createMarshalledReply(responseID, result, jsonErr)
	if err != nil {
		s.logger.Errorf("Failed to marshal reply: %v", err)
		return
	}

	// Write the response.
	err = s.writeHTTPResponseHeaders(r, w.Header(), http.StatusOK, buf)
	if err != nil {
		s.logger.Errorf("Error writing HTTPResponseHeaders: %v", err)
		return
	}

	if _, err := buf.Write(msg); err != nil {
		s.logger.Errorf("Failed to write marshalled reply: %v", err)
	}

	// Terminate with newline to maintain compatibility with Bitcoin Core.
	if err := buf.WriteByte('\n'); err != nil {
		s.logger.Errorf("Failed to append terminating newline to reply: %v", err)
	}
}

// jsonAuthFail sends a message back to the client if the http auth is rejected.
func jsonAuthFail(w http.ResponseWriter) {
	w.Header().Add("WWW-Authenticate", `Basic realm="bsvd RPC"`)
	http.Error(w, "401 Unauthorized.", http.StatusUnauthorized)
}

// Start initializes and launches the RPC server, binding to the configured network addresses
// and beginning to accept and process client connections.
//
// This method performs several critical initialization tasks:
// 1. Validates the server has not already been started (using atomic operations)
// 2. Initializes network listeners on all configured interfaces and ports
// 3. Launches goroutines to accept and process incoming connections
// 4. Sets up proper signal handling for clean shutdown
// 5. Signals readiness through the provided channel
//
// The server supports binding to multiple addresses simultaneously, allowing both
// IPv4 and IPv6 connections, as well as restricting access to localhost-only if
// configured for development or testing environments.
//
// Network security considerations:
// - Proper error handling for network binding failures
// - Systematic tracking of all launched goroutines for clean shutdown
// - Validation of TLS configuration when secure connections are enabled
//
// Parameters:
//   - ctx: Context for cancellation and tracing
//   - readyCh: Channel to signal when the server is ready to accept connections
//
// Returns:
//   - error: Any error encountered during startup, nil on success
func (s *RPCServer) Start(ctx context.Context, readyCh chan<- struct{}) error {
	var closeOnce sync.Once
	defer closeOnce.Do(func() { close(readyCh) })

	if atomic.AddInt32(&s.started, 1) != 1 {
		return nil
	}

	// TODO (GOKHAN): Discuss if we need to restore the node before starting the RPC server
	// Currently we are starting the RPC server even if we are in the RESTORE mode
	/*
		// Check if we need to Restore. If so, move FSM to the Restore state
		// Restore will block and wait for RUN event to be manually sent
		// TODO: think if we can automate transition to RUN state after restore is complete.
		if s.settings.BlockChain.FSMStateRestore {
			// Send Restore event to FSM
			_, err := s.blockchainClient.Restore(ctx, &emptypb.Empty{})
			if err != nil {
				v.logger.Errorf("[RPC] failed to send Restore event [%v], this should not happen, FSM will continue without Restoring", err)
			}

			// Wait for node to finish Restoring.
			// this means FSM got a RUN event and transitioned to RUN state
			// this will block
			s.logger.Infof("[RPC] Node is restoring, waiting for FSM to transition to Running state")
			_ = s.blockchainClient.WaitForFSMtoTransitionToGivenState(ctx, blockchain_api.FSMStateType_RUNNING)
			s.logger.Infof("[RPC] Node finished restoring and has transitioned to Running state, continuing to start Asset service")
		}
	*/

	s.logger.Infof("Starting RPC server")

	rpcServeMux := http.NewServeMux()
	httpServer := &http.Server{
		Handler: rpcServeMux,

		// Timeout connections which don't complete the initial
		// handshake within the allowed timeframe.
		ReadTimeout: time.Second * rpcAuthTimeoutSeconds,
	}

	rpcServeMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// Extract trace context from incoming request headers
		ctx := r.Context()
		ctx = otel.GetTextMapPropagator().Extract(ctx, propagation.HeaderCarrier(r.Header))

		// Update request with the new context
		r = r.WithContext(ctx)

		w.Header().Set("Connection", "close")
		w.Header().Set("Content-Type", "application/json")

		r.Close = true

		// Limit the number of connections to max allowed.
		if s.limitConnections(w, r.RemoteAddr) {
			return
		}

		// Keep track of the number of connected clients.
		s.incrementClients()
		defer s.decrementClients()
		_, isAdmin, err := s.checkAuth(r, true)

		if err != nil {
			jsonAuthFail(w)
			return
		}

		// Read and respond to the request.
		s.jsonRPCRead(w, r, isAdmin)
	})

	for _, listener := range s.listeners {
		s.wg.Add(1)

		go func(listener net.Listener) {
			defer s.wg.Done() // Ensure wg.Done() is called when the goroutine exits.

			// Shutdown logic when context is cancelled
			go func() {
				<-ctx.Done() // Wait for the context to be cancelled.
				s.logger.Infof("Shutting down RPC server on %s", listener.Addr())

				shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // 5-second timeout for shutdown
				defer cancel()

				if err := httpServer.Shutdown(shutdownCtx); err != nil {
					s.logger.Errorf("RPC server shutdown error: %v", err)
				}
			}()

			closeOnce.Do(func() { close(readyCh) })

			s.logger.Infof("RPC server listening on %s", listener.Addr())
			_ = httpServer.Serve(listener)
			s.logger.Infof("RPC listener done for %s", listener.Addr())
		}(listener)
	}
	// wait for the server to finish
	<-ctx.Done()
	s.logger.Infof("Main context cancelled. Waiting for servers to complete...")

	// Wait for all servers to complete
	s.wg.Wait()

	s.logger.Infof("All servers have shut down gracefully.")

	return nil
}

// NewServer creates and configures a new RPC server instance with the given dependencies.
//
// This factory function creates a fully configured RPCServer instance, setting up:
// - Authentication credentials from settings
// - Connection limits and parameters
// - Command handlers and help text
// - Client connections to required services
//
// The RPC server requires connections to several other Teranode services to function
// properly, as it primarily serves as an API gateway to underlying node functionality.
// These dependencies are injected through this constructor to maintain proper
// service separation and testability.
//
// Parameters:
//   - logger: Structured logger for operational and debug messages
//   - tSettings: Configuration settings for the RPC server and related features
//   - blockchainClient: Interface to the blockchain service for block and chain operations
//   - utxoStore: Interface to the UTXO database for transaction validation
//
// Returns:
//   - *RPCServer: Configured server instance ready for initialization
//   - error: Any error encountered during configuration
func NewServer(logger ulogger.Logger, tSettings *settings.Settings, blockchainClient blockchain.ClientI, blockValidationClient blockvalidation.Interface, utxoStore utxo.Store, blockAssemblyClient blockassembly.ClientI, peerClient peer.ClientI, p2pClient p2p.ClientI) (*RPCServer, error) {
	initPrometheusMetrics()

	assetHTTPAddress := tSettings.Asset.HTTPAddress
	if assetHTTPAddress == "" {
		return nil, errors.NewConfigurationError("missing setting: asset_httpAddress")
	}

	parsedURL, err := url.ParseRequestURI(assetHTTPAddress)
	if err != nil {
		return nil, errors.NewConfigurationError("Invalid URL", err)
	}

	rpc := RPCServer{
		statusLines:            make(map[int]string),
		requestProcessShutdown: make(chan struct{}),
		logger:                 logger,
		settings:               tSettings,
		quit:                   make(chan int),
		blockchainClient:       blockchainClient,
		blockValidationClient:  blockValidationClient,
		assetHTTPURL:           parsedURL,
		helpCacher:             newHelpCacher(),
		utxoStore:              utxoStore,
		blockAssemblyClient:    blockAssemblyClient,
		peerClient:             peerClient,
		p2pClient:              p2pClient,
	}

	rpcUser := tSettings.RPC.RPCUser
	if rpcUser == "" {
		logger.Warnf("rpc_user not set in config")
	}

	rpcPass := tSettings.RPC.RPCPass
	if rpcPass == "" {
		logger.Warnf("rpc_pass not set in config")
	}

	rpcLimitUser := tSettings.RPC.RPCLimitUser
	if rpcLimitUser == "" {
		logger.Warnf("rpc_limit_user not set in config")
	}

	rpcLimitPass := tSettings.RPC.RPCLimitPass
	if rpcLimitPass == "" {
		logger.Warnf("rpc_limit_pass not set in config")
	}

	if rpcUser != "" && rpcPass != "" {
		login := rpcUser + ":" + rpcPass
		auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
		rpc.authsha = sha256.Sum256([]byte(auth))
	}

	if rpcLimitUser != "" && rpcLimitPass != "" {
		login := rpcLimitUser + ":" + rpcLimitPass
		auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
		rpc.limitauthsha = sha256.Sum256([]byte(auth))
	}
	// rpc.cfg.Chain.Subscribe(rpc.handleBlockchainNotification)

	rpc.rpcMaxClients = tSettings.RPC.RPCMaxClients

	rpc.rpcQuirks = tSettings.RPC.RPCQuirks

	rpcListenerURL := tSettings.RPC.RPCListenerURL
	if rpcListenerURL == nil {
		return nil, errors.NewConfigurationError("rpc_listener_url not set in config")
	}

	listener, _, _, err := util.GetListener(tSettings.Context, "rpc", "http://", rpcListenerURL.Host)
	if err != nil {
		logger.Errorf("Error listening: %v", err)
		os.Exit(1)
	}

	rpc.listeners = append(rpc.listeners, listener)

	return &rpc, nil
}

// Init performs second-stage initialization of the RPC server by establishing connections
// to dependent services that weren't available during initial construction.
//
// This method completes the RPC server initialization by:
// 1. Connecting to the Block Assembly service for mining-related operations
// 2. Connecting to the P2P service for network peer management
// 3. Connecting to the Legacy service for compatibility with older protocols
// 4. Refreshing the help cache with complete command information
//
// The initialization is designed to be idempotent and can be safely called multiple times,
// though typically it's only called once after NewServer and before Start.
//
// Parameters:
//   - ctx: Context for cancellation and timeout control
//
// Returns:
//   - error: Any error encountered during initialization
func (s *RPCServer) Init(ctx context.Context) (err error) {
	rpcHandlers = rpcHandlersBeforeInit
	// rand.Seed(time.Now().UnixNano())

	return nil
}

// Health reports the operational status of the RPC service for monitoring and health checking.
//
// This method implements the standard Teranode health checking interface used across all services
// for consistent monitoring, alerting, and orchestration. It provides both readiness and liveness
// checking capabilities to support different operational scenarios:
//
//   - Readiness: Indicates whether the service is ready to accept requests (listeners are bound
//     and core dependencies are available)
//   - Liveness: Indicates whether the service is functioning correctly (listeners are still working
//     and not in a hung state)
//
// The method performs checks appropriate to the service's role, including:
// - Verifying network listeners are active
// - Checking connections to dependent services
// - Validating internal state consistency
//
// Parameters:
//   - ctx: Context for cancellation and timeouts
//   - checkLiveness: If true, performs deeper health checks; if false, only checks readiness
//
// Returns:
//   - int: HTTP status code representing health (200 for healthy, other codes for specific issues)
//   - string: Human-readable description of the current health state
//   - error: Detailed error information if the service is unhealthy
func (s *RPCServer) Health(ctx context.Context, checkLiveness bool) (int, string, error) {
	if checkLiveness {
		// Add liveness checks here. Don't include dependency checks.
		// If the service is stuck return http.StatusServiceUnavailable
		// to indicate a restart is needed
		return http.StatusOK, "OK", nil
	}

	// Add readiness checks here. Include dependency checks.
	// If any dependency is not ready, return http.StatusServiceUnavailable
	// If all dependencies are ready, return http.StatusOK
	// A failed dependency check does not imply the service needs restarting
	checks := make([]health.Check, 0, 3)

	if s.blockchainClient != nil {
		checks = append(checks, health.Check{Name: "BlockchainClient", Check: s.blockchainClient.Health})
		checks = append(checks, health.Check{Name: "FSM", Check: blockchain.CheckFSM(s.blockchainClient)})
	}

	if s.blockAssemblyClient != nil {
		checks = append(checks, health.Check{Name: "BlockAssemblyClient", Check: s.blockAssemblyClient.Health})
	}

	return health.CheckAll(ctx, checkLiveness, checks)
}
