/**
 *  @file
 *  @copyright defined in aergo/LICENSE.txt
 */
package main

import (
	"fmt"
	"github.com/aergoio/aergo-lib/log"
	"github.com/aergoio/aergo/v2/account"
	"github.com/aergoio/aergo/v2/chain"
	polarisclient "github.com/aergoio/aergo/v2/cmd/polaris/client"
	"github.com/aergoio/aergo/v2/config"
	"github.com/aergoio/aergo/v2/consensus"
	"github.com/aergoio/aergo/v2/consensus/impl"
	"github.com/aergoio/aergo/v2/internal/common"
	"github.com/aergoio/aergo/v2/mempool"
	"github.com/aergoio/aergo/v2/p2p"
	"github.com/aergoio/aergo/v2/p2p/p2pkey"
	"github.com/aergoio/aergo/v2/pkg/component"
	"github.com/aergoio/aergo/v2/rpc"
	"github.com/aergoio/aergo/v2/rpc/web3"
	"github.com/aergoio/aergo/v2/syncer"
	"github.com/spf13/cobra"
	"os"
)

var (
	gitRevision, gitBranch string
)

func main() {
	if err := rootCmd.Execute(); err != nil {
		os.Exit(1)
	}
}

var (
	rootCmd = &cobra.Command{
		Use:   "aergosvr",
		Short: "Aergo Server",
		Long:  "Aergo Server Full-node implementation",
		PersistentPreRun: func(cmd *cobra.Command, args []string) {
			if homePath != "" && configFilePath != "" && verbose {
				fmt.Println("ignore home path if given config file has valid configuration")
			}
		},
		Run: rootRun,
	}
	homePath       string
	configFilePath string
	enableTestmode bool
	useTestnet     bool

	verbose bool

	svrlog *log.Logger

	cfg *config.Config
)

func init() {
	cobra.OnInitialize(initConfig)

	localFlags := rootCmd.Flags()
	localFlags.SortFlags = false
	localFlags.BoolVar(&useTestnet, "testnet", false, "use Aergo TestNet; this only affects if there's no genesis block")
	localFlags.BoolVar(&enableTestmode, "testmode", false, "enable unsafe test mode (skips certain validations); can NOT use with --testnet")

	fs := rootCmd.PersistentFlags()
	fs.StringVar(&homePath, "home", "", "path of aergo home")
	fs.StringVar(&configFilePath, "config", "", "path of configuration file")
	fs.BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
}

func initConfig() {
	serverCtx := config.NewServerContext(homePath, configFilePath)
	cfg = serverCtx.GetDefaultConfig().(*config.Config)
	err := serverCtx.LoadOrCreateConfig(cfg)
	if err != nil {
		fmt.Printf("Fail to load configuration file %v: %v", serverCtx.Vc.ConfigFileUsed(), err.Error())
		os.Exit(1)
	}
	if enableTestmode {
		cfg.EnableTestmode = true
	}
	if useTestnet {
		cfg.UseTestnet = true
	}
	if cfg.EnableTestmode && cfg.UseTestnet {
		fmt.Println("Turn off test mode for Aergo Public Chains")
		os.Exit(1)
	}
}

func rootRun(cmd *cobra.Command, args []string) {
	// Set caller info to relative file path
	log.SetRelativeLogPathForProject()

	svrlog = log.NewLogger("asvr")
	svrlog.Info().Str("revision", gitRevision).Str("branch", gitBranch).Msg("AERGO SVR STARTED")

	if cfg.EnableTestmode {
		svrlog.Warn().Msgf("Running with unsafe test mode. Turn off test mode for production use!")
	}

	p2pkey.InitNodeInfo(&cfg.BaseConfig, cfg.P2P, githash, svrlog)

	compMng := component.NewComponentHub()

	chainSvc := chain.NewChainService(cfg)

	mpoolSvc := mempool.NewMemPoolService(cfg, chainSvc)
	rpcSvc := rpc.NewRPC(cfg, chainSvc, githash)
	admSvc := rpc.NewAdminService(cfg.RPC, compMng)
	syncSvc := syncer.NewSyncer(cfg, chainSvc, nil)
	p2pSvc := p2p.NewP2P(cfg, chainSvc)
	pmapSvc := polarisclient.NewPolarisConnectSvc(cfg.P2P, p2pSvc)

	var accountSvc component.IComponent
	if cfg.Personal {
		accountSvc = account.NewAccountService(cfg, chainSvc.SDB())
	}

	var web3Svr component.IComponent
	if cfg.Web3.Enable {
		web3Svr = web3.NewWeb3(cfg, rpcSvc.GetActualServer())
	}

	// Register services to Hub. Don't need to do nil-check since Register
	// function skips nil parameters.
	var verifyOnly = cfg.Blockchain.VerifyOnly || cfg.Blockchain.VerifyBlock != 0
	if !verifyOnly {
		compMng.Register(chainSvc, mpoolSvc, rpcSvc, web3Svr, syncSvc, p2pSvc, accountSvc, pmapSvc)
	} else {
		compMng.Register(chainSvc, mpoolSvc, rpcSvc, web3Svr)
	}

	consensusSvc, err := impl.New(cfg, compMng, chainSvc, p2pSvc, rpcSvc)
	if err != nil {
		svrlog.Error().Err(err).Msg("Failed to start consensus service.")
		os.Exit(1)
	}

	dmp := NewDumper(cfg, compMng)

	// All the services objects including Consensus must be created before the
	// actors are started.
	compMng.Start()

	if cfg.Consensus.EnableBp {
		// Warning: The consensus service must start after all the other
		// services.
		consensus.Start(consensusSvc)
	}

	if cfg.EnableDump {
		dmp.Start()
	}

	if len(cfg.RPC.NetServicePath) > 0 {
		admSvc.Start()
	}

	var interrupt = common.HandleKillSig(func() {
		consensus.Stop(consensusSvc)
		compMng.Stop()
	}, svrlog)

	// Wait main routine to stop
	<-interrupt.C
}
