//go:build !js && !wasm

package main

import (
	"bytes"
	"crypto/rand"
	"encoding/binary"
	"encoding/hex"
	"flag"
	"fmt"
	"io/fs"
	"log"
	"math/big"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	rdebug "runtime/debug"
	"runtime/pprof"
	"strconv"
	"strings"
	"syscall"
	"time"

	"go.uber.org/zap"
	"golang.org/x/crypto/sha3"
	"google.golang.org/protobuf/proto"
	"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
	"source.quilibrium.com/quilibrium/monorepo/node/utils"

	"github.com/cloudflare/circl/sign/ed448"
	"github.com/libp2p/go-libp2p/core/crypto"
	"github.com/libp2p/go-libp2p/core/peer"
	"github.com/pbnjay/memory"
	"github.com/pkg/errors"
	"source.quilibrium.com/quilibrium/monorepo/node/app"
	"source.quilibrium.com/quilibrium/monorepo/node/config"
	qcrypto "source.quilibrium.com/quilibrium/monorepo/node/crypto"
	"source.quilibrium.com/quilibrium/monorepo/node/crypto/kzg"
	"source.quilibrium.com/quilibrium/monorepo/node/rpc"
)

var (
	configDirectory = flag.String(
		"config",
		filepath.Join(".", ".config"),
		"the configuration directory",
	)
	balance = flag.Bool(
		"balance",
		false,
		"print the node's confirmed token balance to stdout and exit",
	)
	dbConsole = flag.Bool(
		"db-console",
		false,
		"starts the node in database console mode",
	)
	importPrivKey = flag.String(
		"import-priv-key",
		"",
		"creates a new config using a specific key from the phase one ceremony",
	)
	peerId = flag.Bool(
		"peer-id",
		false,
		"print the peer id to stdout from the config and exit",
	)
	cpuprofile = flag.String(
		"cpuprofile",
		"",
		"write cpu profile to file",
	)
	memprofile = flag.String(
		"memprofile",
		"",
		"write memory profile after 20m to this file",
	)
	nodeInfo = flag.Bool(
		"node-info",
		false,
		"print node related information",
	)
	debug = flag.Bool(
		"debug",
		false,
		"sets log output to debug (verbose)",
	)
	dhtOnly = flag.Bool(
		"dht-only",
		false,
		"sets a node to run strictly as a dht bootstrap peer (not full node)",
	)
	network = flag.Uint(
		"network",
		0,
		"sets the active network for the node (mainnet = 0, primary testnet = 1)",
	)
	signatureCheck = flag.Bool(
		"signature-check",
		signatureCheckDefault(),
		"enables or disables signature validation (default true or value of QUILIBRIUM_SIGNATURE_CHECK env var)",
	)
	core = flag.Int(
		"core",
		0,
		"specifies the core of the process (defaults to zero, the initial launcher)",
	)
	parentProcess = flag.Int(
		"parent-process",
		0,
		"specifies the parent process pid for a data worker",
	)
	integrityCheck = flag.Bool(
		"integrity-check",
		false,
		"runs an integrity check on the store, helpful for confirming backups are not corrupted (defaults to false)",
	)
)

var signatories = []string{
	"b1214da7f355f5a9edb7bcc23d403bdf789f070cca10db2b4cadc22f2d837afb650944853e35d5f42ef3c4105b802b144b4077d5d3253e4100",
	"de4cfe7083104bfe32f0d4082fa0200464d8b10804a811653eedda376efcad64dd222f0f0ceb0b8ae58abe830d7a7e3f3b2d79d691318daa00",
	"540237a35e124882d6b64e7bb5718273fa338e553f772b77fe90570e45303762b34131bdcb6c0b9f2cf9e393d9c7e0f546eeab0bcbbd881680",
	"fbe4166e37f93f90d2ebf06305315ae11b37e501d09596f8bde11ba9d343034fbca80f252205aa2f582a512a72ad293df371baa582da072900",
	"4160572e493e1bf15c44e055b11bf75230c76c7d2c67b48066770ab03dfd5ed34c97b9a431ec18578c83a0df9250b8362c38068650e8b01400",
	"45170b626884b85d61ae109f2aa9b0e1ecc18b181508431ea6308f3869f2adae49da9799a0a594eaa4ef3ad492518fb1729decd44169d40d00",
	"92cd8ee5362f3ae274a75ab9471024dbc144bff441ed8af7d19750ac512ff51e40e7f7b01e4f96b6345dd58878565948c3eb52c53f250b5080",
	"001a4cbfce5d9aeb7e20665b0d236721b228a32f0baee62ffa77f45b82ecaf577e8a38b7ef91fcf7d2d2d2b504f085461398d30b24abb1d700",
	"65b835071731c6e785bb2d107c7d85d8a537d79c435c3f42bb2f87027f93f858d7b37c598cef267a5db46e345f7a6f81969b465686657d1e00",
	"4507626f7164e7d8c304c07ff8d2e23c113fe108b221d2e60672f4d07750345815e2b4b3cc3df4d3466bf2f669c35c3172e06511270612ab00",
	"4fb2537345e46be3d5f96340c1441007501702dd5bfaf6dbf6943bbefceca8fb2b94ec0a8a1a2f49850fbe1d10244889a4f40abfa9e0c9e000",
	"57be2861faf0fffcbfd122c85c77010dce8f213030905781b85b6f345d912c7b5ace17797d9810899dfb8d13e7c8369595740725ab3dd5bd00",
	"61628beef8f6964466fd078d6a2b90a397ab0777a14b9728227fd19f36752f9451b1a8d780740a0b9a8ce3df5f89ca7b9ff17de9274a270980",
	"5547afc71b02821e2f5bfdd30fbe1374c3853898deff20a1b5cc729b8e81670fbbb9d1e917f85d153ea4b26bbf6f9c546dc1b64b9916608d80",
	"81d63a45f068629f568de812f18be5807bfe828a830097f09cf02330d6acd35e3607401df3fda08b03b68ea6e68afd506b23506b11e87a0f80",
	"6e2872f73c4868c4286bef7bfe2f5479a41c42f4e07505efa4883c7950c740252e0eea78eef10c584b19b1dcda01f7767d3135d07c33244100",
	"a114b061f8d35e3f3497c8c43d83ba6b4af67aa7b39b743b1b0a35f2d66110b5051dd3d86f69b57122a35b64e624b8180bee63b6152fce4280",
}

func signatureCheckDefault() bool {
	envVarValue, envVarExists := os.LookupEnv("QUILIBRIUM_SIGNATURE_CHECK")
	if envVarExists {
		def, err := strconv.ParseBool(envVarValue)
		if err == nil {
			return def
		} else {
			fmt.Println("Invalid environment variable QUILIBRIUM_SIGNATURE_CHECK, must be 'true' or 'false'. Got: " + envVarValue)
		}
	}

	return true
}

func main() {
	flag.Parse()

	if *signatureCheck {
		if runtime.GOOS == "windows" {
			fmt.Println("Signature check not available for windows yet, skipping...")
		} else {
			ex, err := os.Executable()
			if err != nil {
				panic(err)
			}

			b, err := os.ReadFile(ex)
			if err != nil {
				fmt.Println(
					"Error encountered during signature check – are you running this " +
						"from source? (use --signature-check=false)",
				)
				panic(err)
			}

			checksum := sha3.Sum256(b)
			digest, err := os.ReadFile(ex + ".dgst")
			if err != nil {
				fmt.Println("Digest file not found")
				os.Exit(1)
			}

			parts := strings.Split(string(digest), " ")
			if len(parts) != 2 {
				fmt.Println("Invalid digest file format")
				os.Exit(1)
			}

			digestBytes, err := hex.DecodeString(parts[1][:64])
			if err != nil {
				fmt.Println("Invalid digest file format")
				os.Exit(1)
			}

			if !bytes.Equal(checksum[:], digestBytes) {
				fmt.Println("Invalid digest for node")
				os.Exit(1)
			}

			count := 0

			for i := 1; i <= len(signatories); i++ {
				signatureFile := fmt.Sprintf(ex+".dgst.sig.%d", i)
				sig, err := os.ReadFile(signatureFile)
				if err != nil {
					continue
				}

				pubkey, _ := hex.DecodeString(signatories[i-1])
				if !ed448.Verify(pubkey, digest, sig, "") {
					fmt.Printf("Failed signature check for signatory #%d\n", i)
					os.Exit(1)
				}
				count++
			}

			if count < len(signatories)/2+len(signatories)%2 {
				fmt.Printf("Quorum on signatures not met")
				os.Exit(1)
			}

			fmt.Println("Signature check passed")
		}
	} else {
		fmt.Println("Signature check disabled, skipping...")
	}

	if *memprofile != "" && *core == 0 {
		go func() {
			for {
				time.Sleep(5 * time.Minute)
				f, err := os.Create(*memprofile)
				if err != nil {
					log.Fatal(err)
				}
				pprof.WriteHeapProfile(f)
				f.Close()
			}
		}()
	}

	if *cpuprofile != "" && *core == 0 {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	if *balance {
		config, err := config.LoadConfig(*configDirectory, "")
		if err != nil {
			panic(err)
		}

		printBalance(config)

		return
	}

	if *peerId {
		config, err := config.LoadConfig(*configDirectory, "")
		if err != nil {
			panic(err)
		}

		printPeerID(config.P2P)
		return
	}

	if *importPrivKey != "" {
		config, err := config.LoadConfig(*configDirectory, *importPrivKey)
		if err != nil {
			panic(err)
		}

		printPeerID(config.P2P)
		fmt.Println("Import completed, you are ready for the launch.")
		return
	}

	if *nodeInfo {
		config, err := config.LoadConfig(*configDirectory, "")
		if err != nil {
			panic(err)
		}

		printNodeInfo(config)
		return
	}

	if !*dbConsole && *core == 0 {
		printLogo()
		printVersion()
		fmt.Println(" ")
	}

	nodeConfig, err := config.LoadConfig(*configDirectory, "")
	if err != nil {
		panic(err)
	}

	if *network != 0 {
		if nodeConfig.P2P.BootstrapPeers[0] == config.BootstrapPeers[0] {
			fmt.Println(
				"Node has specified to run outside of mainnet but is still " +
					"using default bootstrap list. This will fail. Exiting.",
			)
			os.Exit(1)
		}

		nodeConfig.Engine.GenesisSeed = fmt.Sprintf(
			"%02x%s",
			byte(*network),
			nodeConfig.Engine.GenesisSeed,
		)
		nodeConfig.P2P.Network = uint8(*network)
		fmt.Println(
			"Node is operating outside of mainnet – be sure you intended to do this.",
		)
	}

	clearIfTestData(*configDirectory, nodeConfig)

	if *dbConsole {
		console, err := app.NewDBConsole(nodeConfig)
		if err != nil {
			panic(err)
		}

		console.Run()
		return
	}

	if *dhtOnly {
		done := make(chan os.Signal, 1)
		signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
		dht, err := app.NewDHTNode(nodeConfig)
		if err != nil {
			panic(err)
		}

		go func() {
			dht.Start()
		}()

		<-done
		dht.Stop()
		return
	}

	if *core != 0 {
		runtime.GOMAXPROCS(1)
		rdebug.SetGCPercent(9999)

		if nodeConfig.Engine.DataWorkerMemoryLimit == 0 {
			nodeConfig.Engine.DataWorkerMemoryLimit = 1792 * 1024 * 1024 // 1.75GiB
		}

		rdebug.SetMemoryLimit(nodeConfig.Engine.DataWorkerMemoryLimit)

		if nodeConfig.Engine.DataWorkerBaseListenMultiaddr == "" {
			nodeConfig.Engine.DataWorkerBaseListenMultiaddr = "/ip4/127.0.0.1/tcp/%d"
		}

		if nodeConfig.Engine.DataWorkerBaseListenPort == 0 {
			nodeConfig.Engine.DataWorkerBaseListenPort = 40000
		}

		if *parentProcess == 0 && len(nodeConfig.Engine.DataWorkerMultiaddrs) == 0 {
			panic("parent process pid not specified")
		}

		l, err := zap.NewProduction()
		if err != nil {
			panic(err)
		}

		rpcMultiaddr := fmt.Sprintf(
			nodeConfig.Engine.DataWorkerBaseListenMultiaddr,
			int(nodeConfig.Engine.DataWorkerBaseListenPort)+*core-1,
		)

		if len(nodeConfig.Engine.DataWorkerMultiaddrs) != 0 {
			rpcMultiaddr = nodeConfig.Engine.DataWorkerMultiaddrs[*core-1]
		}

		srv, err := rpc.NewDataWorkerIPCServer(
			rpcMultiaddr,
			l,
			uint32(*core)-1,
			qcrypto.NewWesolowskiFrameProver(l),
			*parentProcess,
		)
		if err != nil {
			panic(err)
		}

		err = srv.Start()
		if err != nil {
			panic(err)
		}
		return
	}

	fmt.Println("Loading ceremony state and starting node...")

	if !*integrityCheck {
		go spawnDataWorkers(nodeConfig)
	}

	kzg.Init()

	report := RunSelfTestIfNeeded(*configDirectory, nodeConfig)
	RunMigrationIfNeeded(*configDirectory, nodeConfig)

	done := make(chan os.Signal, 1)
	signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
	var node *app.Node
	if *debug {
		node, err = app.NewDebugNode(nodeConfig, report)
	} else {
		node, err = app.NewNode(nodeConfig, report)
	}

	if err != nil {
		panic(err)
	}

	if *integrityCheck {
		fmt.Println("Running integrity check...")
		node.VerifyProofIntegrity()
		fmt.Println("Integrity check passed!")
		return
	}

	repair(*configDirectory, node)
	runtime.GOMAXPROCS(1)

	if nodeConfig.ListenGRPCMultiaddr != "" {
		srv, err := rpc.NewRPCServer(
			nodeConfig.ListenGRPCMultiaddr,
			nodeConfig.ListenRestMultiaddr,
			node.GetLogger(),
			node.GetDataProofStore(),
			node.GetClockStore(),
			node.GetKeyManager(),
			node.GetPubSub(),
			node.GetMasterClock(),
			node.GetExecutionEngines(),
		)
		if err != nil {
			panic(err)
		}

		go func() {
			err := srv.Start()
			if err != nil {
				panic(err)
			}
		}()
	}

	node.Start()

	<-done
	stopDataWorkers()
	node.Stop()
}

var dataWorkers []*exec.Cmd

func spawnDataWorkers(nodeConfig *config.Config) {
	if len(nodeConfig.Engine.DataWorkerMultiaddrs) != 0 {
		fmt.Println(
			"Data workers configured by multiaddr, be sure these are running...",
		)
		return
	}

	process, err := os.Executable()
	if err != nil {
		panic(err)
	}

	cores := runtime.GOMAXPROCS(0)
	dataWorkers = make([]*exec.Cmd, cores-1)
	fmt.Printf("Spawning %d data workers...\n", cores-1)

	for i := 1; i <= cores-1; i++ {
		i := i
		go func() {
			for {
				args := []string{
					fmt.Sprintf("--core=%d", i),
					fmt.Sprintf("--parent-process=%d", os.Getpid()),
				}
				args = append(args, os.Args[1:]...)
				cmd := exec.Command(process, args...)
				cmd.Stdout = os.Stdout
				cmd.Stderr = os.Stdout
				err := cmd.Start()
				if err != nil {
					panic(err)
				}

				dataWorkers[i-1] = cmd
				cmd.Wait()
				time.Sleep(25 * time.Millisecond)
				fmt.Printf("Data worker %d stopped, restarting...\n", i)
			}
		}()
	}
}

func stopDataWorkers() {
	for i := 0; i < len(dataWorkers); i++ {
		err := dataWorkers[i].Process.Signal(os.Kill)
		if err != nil {
			fmt.Printf(
				"fatal: unable to kill worker with pid %d, please kill this process!\n",
				dataWorkers[i].Process.Pid,
			)
		}
	}
}

func RunMigrationIfNeeded(
	configDir string,
	nodeConfig *config.Config,
) {
	shouldMigrate := false
	migrationInfo := []byte{0x00, 0x00, 0x00}
	_, err := os.Stat(filepath.Join(configDir, "MIGRATIONS"))
	if err != nil && os.IsNotExist(err) {
		fmt.Println("Migrations file not found, will perform migration...")
		shouldMigrate = true
	}

	if !shouldMigrate {
		migrationInfo, err = os.ReadFile(filepath.Join(configDir, "MIGRATIONS"))
		if err != nil {
			panic(err)
		}

		if len(migrationInfo) < 3 ||
			!bytes.Equal(migrationInfo, []byte{0x01, 0x04, 0x013}) {
			fmt.Println("Migrations file outdated, will perform migration...")
			shouldMigrate = true
		}
	}

	// If subsequent migrations arise, we will need to distinguish by version
	if shouldMigrate {
		fmt.Println("Running migration...")

		// Easiest migration in the world.
		err := os.RemoveAll(filepath.Join(configDir, "store"))
		if err != nil {
			fmt.Println("ERROR: Could not remove store, please be sure to do this before restarting the node.")
			panic(err)
		}

		err = os.WriteFile(
			filepath.Join(configDir, "MIGRATIONS"),
			[]byte{0x01, 0x04, 0x13},
			fs.FileMode(0600),
		)
		if err != nil {
			fmt.Println("ERROR: Could not save migration file.")
			panic(err)
		}

		fmt.Println("Migration completed.")
	}
}

func RunSelfTestIfNeeded(
	configDir string,
	nodeConfig *config.Config,
) *protobufs.SelfTestReport {
	logger, _ := zap.NewProduction()

	cores := runtime.GOMAXPROCS(0)
	if len(nodeConfig.Engine.DataWorkerMultiaddrs) != 0 {
		cores = len(nodeConfig.Engine.DataWorkerMultiaddrs) + 1
	}

	memory := memory.TotalMemory()
	d, err := os.Stat(filepath.Join(configDir, "store"))
	if d == nil {
		err := os.Mkdir(filepath.Join(configDir, "store"), 0755)
		if err != nil {
			panic(err)
		}
	}

	f, err := os.Stat(filepath.Join(configDir, "SELF_TEST"))

	if f != nil {
		if f.Size() != 0 {
			report := &protobufs.SelfTestReport{}

			selfTestBytes, err := os.ReadFile(filepath.Join(configDir, "SELF_TEST"))
			if err != nil {
				panic(err)
			}

			err = proto.Unmarshal(selfTestBytes, report)
			if err != nil {
				panic(err)
			}

			if report.Cores == uint32(cores) &&
				binary.BigEndian.Uint64(report.Memory) == memory {
				return report
			}
		}
		logger.Info("no self-test report found, generating")
	}

	report := &protobufs.SelfTestReport{}
	difficulty := nodeConfig.Engine.Difficulty
	if difficulty == 0 || difficulty == 10000 {
		difficulty = 100000
	}
	report.Difficulty = difficulty

	frameProver := qcrypto.NewWesolowskiFrameProver(logger)

	logger.Info("generating difficulty metric")

	start := time.Now().UnixMilli()
	_, err = frameProver.ProveMasterClockFrame(
		&protobufs.ClockFrame{
			Filter:         []byte{0x00},
			FrameNumber:    0,
			Timestamp:      0,
			Difficulty:     difficulty,
			ParentSelector: []byte{0x00},
			Input:          make([]byte, 516),
			Output:         make([]byte, 516),
		},
		0,
		difficulty,
	)
	if err != nil {
		panic(err)
	}
	end := time.Now().UnixMilli()
	report.DifficultyMetric = end - start

	logger.Info("generating entropy for commit/proof sizes")

	p16bytes := make([]byte, 1024)
	p128bytes := make([]byte, 8192)
	p1024bytes := make([]byte, 65536)
	p65536bytes := make([]byte, 4194304)
	rand.Read(p16bytes)
	rand.Read(p128bytes)
	rand.Read(p1024bytes)
	rand.Read(p65536bytes)
	kzgProver := qcrypto.NewKZGInclusionProver(logger)

	logger.Info("generating 16 degree commitment metric")
	start = time.Now().UnixMilli()
	_, err = kzgProver.CommitRaw(p16bytes, 16)
	if err != nil {
		panic(err)
	}
	end = time.Now().UnixMilli()
	report.Commit_16Metric = end - start

	logger.Info("generating 128 degree commitment metric")
	start = time.Now().UnixMilli()
	_, err = kzgProver.CommitRaw(p128bytes, 128)
	if err != nil {
		panic(err)
	}
	end = time.Now().UnixMilli()
	report.Commit_128Metric = end - start

	logger.Info("generating 1024 degree commitment metric")
	start = time.Now().UnixMilli()
	_, err = kzgProver.CommitRaw(p1024bytes, 1024)
	if err != nil {
		panic(err)
	}
	end = time.Now().UnixMilli()
	report.Commit_1024Metric = end - start

	logger.Info("generating 65536 degree commitment metric")
	start = time.Now().UnixMilli()
	_, err = kzgProver.CommitRaw(p65536bytes, 65536)
	if err != nil {
		panic(err)
	}
	end = time.Now().UnixMilli()
	report.Commit_65536Metric = end - start

	logger.Info("generating 16 degree proof metric")
	start = time.Now().UnixMilli()
	_, err = kzgProver.ProveRaw(p16bytes, 0, 16)
	if err != nil {
		panic(err)
	}
	end = time.Now().UnixMilli()
	report.Proof_16Metric = end - start

	logger.Info("generating 128 degree proof metric")
	start = time.Now().UnixMilli()
	_, err = kzgProver.ProveRaw(p128bytes, 0, 128)
	if err != nil {
		panic(err)
	}
	end = time.Now().UnixMilli()
	report.Proof_128Metric = end - start

	logger.Info("generating 1024 degree proof metric")
	start = time.Now().UnixMilli()
	_, err = kzgProver.ProveRaw(p1024bytes, 0, 1024)
	if err != nil {
		panic(err)
	}
	end = time.Now().UnixMilli()
	report.Proof_1024Metric = end - start

	logger.Info("generating 65536 degree proof metric")
	start = time.Now().UnixMilli()
	_, err = kzgProver.ProveRaw(p65536bytes, 0, 65536)
	if err != nil {
		panic(err)
	}
	end = time.Now().UnixMilli()
	report.Proof_65536Metric = end - start

	report.Cores = uint32(cores)
	report.Memory = binary.BigEndian.AppendUint64([]byte{}, memory)
	disk := utils.GetDiskSpace(nodeConfig.DB.Path)
	report.Storage = binary.BigEndian.AppendUint64([]byte{}, disk)
	logger.Info("writing report")

	// tag: dusk – capabilities report in v1.5
	reportBytes, err := proto.Marshal(report)
	if err != nil {
		panic(err)
	}

	err = os.WriteFile(
		filepath.Join(configDir, "SELF_TEST"),
		reportBytes,
		fs.FileMode(0600),
	)
	if err != nil {
		panic(err)
	}

	return report
}

func clearIfTestData(configDir string, nodeConfig *config.Config) {
	_, err := os.Stat(filepath.Join(configDir, "RELEASE_VERSION"))
	if os.IsNotExist(err) {
		fmt.Println("Clearing test data...")
		err := os.RemoveAll(nodeConfig.DB.Path)
		if err != nil {
			panic(err)
		}

		versionFile, err := os.OpenFile(
			filepath.Join(configDir, "RELEASE_VERSION"),
			os.O_CREATE|os.O_RDWR,
			fs.FileMode(0600),
		)
		if err != nil {
			panic(err)
		}

		_, err = versionFile.Write([]byte{0x01, 0x00, 0x00})
		if err != nil {
			panic(err)
		}

		err = versionFile.Close()
		if err != nil {
			panic(err)
		}
	}
}

func repair(configDir string, node *app.Node) {
	_, err := os.Stat(filepath.Join(configDir, "REPAIR"))
	if os.IsNotExist(err) {
		node.RunRepair()

		repairFile, err := os.OpenFile(
			filepath.Join(configDir, "REPAIR"),
			os.O_CREATE|os.O_RDWR,
			fs.FileMode(0600),
		)
		if err != nil {
			panic(err)
		}

		_, err = repairFile.Write([]byte{0x00, 0x00, 0x01})
		if err != nil {
			panic(err)
		}

		err = repairFile.Close()
		if err != nil {
			panic(err)
		}
	}
}

func printBalance(config *config.Config) {
	if config.ListenGRPCMultiaddr == "" {
		_, _ = fmt.Fprintf(os.Stderr, "gRPC Not Enabled, Please Configure\n")
		os.Exit(1)
	}

	conn, err := app.ConnectToNode(config)
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	client := protobufs.NewNodeServiceClient(conn)

	balance, err := app.FetchTokenBalance(client)
	if err != nil {
		panic(err)
	}

	// fmt.Println("Owned balance:", balance.Owned, "QUIL")
	conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
	r := new(big.Rat).SetFrac(balance.UnconfirmedOwned, conversionFactor)
	fmt.Println("Note: Balance is strictly rewards earned with 1.4.19+, check https://www.quilibrium.com/rewards for more info about previous rewards.")
	fmt.Println("Unclaimed balance:", r.FloatString(12), "QUIL")
}

func printPeerID(p2pConfig *config.P2PConfig) {
	peerPrivKey, err := hex.DecodeString(p2pConfig.PeerPrivKey)
	if err != nil {
		panic(errors.Wrap(err, "error unmarshaling peerkey"))
	}

	privKey, err := crypto.UnmarshalEd448PrivateKey(peerPrivKey)
	if err != nil {
		panic(errors.Wrap(err, "error unmarshaling peerkey"))
	}

	pub := privKey.GetPublic()
	id, err := peer.IDFromPublicKey(pub)
	if err != nil {
		panic(errors.Wrap(err, "error getting peer id"))
	}

	fmt.Println("Peer ID: " + id.String())
}

func printNodeInfo(cfg *config.Config) {
	if cfg.ListenGRPCMultiaddr == "" {
		_, _ = fmt.Fprintf(os.Stderr, "gRPC Not Enabled, Please Configure\n")
		os.Exit(1)
	}

	printPeerID(cfg.P2P)

	conn, err := app.ConnectToNode(cfg)
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	client := protobufs.NewNodeServiceClient(conn)

	nodeInfo, err := app.FetchNodeInfo(client)
	if err != nil {
		panic(err)
	}

	fmt.Println("Version: " + config.FormatVersion(nodeInfo.Version))
	fmt.Println("Max Frame: " + strconv.FormatUint(nodeInfo.GetMaxFrame(), 10))
	fmt.Println("Peer Score: " + strconv.FormatUint(nodeInfo.GetPeerScore(), 10))
	printBalance(cfg)
}

func printLogo() {
	fmt.Println("                                   ..-------..")
	fmt.Println("                          ..---''''           ''''---..")
	fmt.Println("                    .---''                             ''---.")
	fmt.Println("                 .-'                                         '-.")
	fmt.Println("             ..-'            ..--''''''''''''''--..             '-..")
	fmt.Println("           .'           .--''                      ''--.            ''.")
	fmt.Println("        .''         ..-'                                ''-.           '.")
	fmt.Println("       '           '                                        ''.          '.")
	fmt.Println("     ''         .''                                            '.          '")
	fmt.Println("    '         ''                                                 '.         '")
	fmt.Println("   '         '                     ##########                      .         '")
	fmt.Println("  '         '                    ##############                     '         '")
	fmt.Println(" '         '                     ##############                      '        '")
	fmt.Println(" '        '                      ##############                      '         '")
	fmt.Println("'        '                         ##########                         '        '")
	fmt.Println("'        '                                                            '        '")
	fmt.Println("'        '                                                            '        '")
	fmt.Println("'        '                    #######      #######                    '        '")
	fmt.Println("'        '                 &#########################                 '        '")
	fmt.Println("'         '              ##############% ##############              '         '")
	fmt.Println(" '         '          &##############      ###############           '        '")
	fmt.Println("  '         '       ###############           ##############%       '.        '")
	fmt.Println("   '         '.       ##########                ###############       '-.    '")
	fmt.Println("    '.         .         #####                     ##############%       '-.'")
	fmt.Println("      '         '.                                   ###############")
	fmt.Println("       '.         '..                                   ##############%")
	fmt.Println("         '.          '-.                                  ###############")
	fmt.Println("           '-.          ''-..                      ..        ##############%")
	fmt.Println("              '-.            ''---............----'  '.        ###############")
	fmt.Println("                 '-..                                  '.        ############")
	fmt.Println("                     ''-..                             ..'         ########")
	fmt.Println("                          ''---..              ...---''               ##")
	fmt.Println("                                 ''----------''")
}

func printVersion() {
	patch := config.GetPatchNumber()
	patchString := ""
	if patch != 0x00 {
		patchString = fmt.Sprintf("-p%d", patch)
	}
	fmt.Println(" ")
	fmt.Println("                      Quilibrium Node - v" + config.GetVersionString() + patchString + " – Centauri")
}
