package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"sync"
	"sync/atomic"

	"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
	irMetrics "git.frostfs.info/TrueCloudLab/frostfs-node/internal/metrics"
	"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/sdnotify"
	"github.com/spf13/viper"
	"go.uber.org/zap"
)

const (
	// ErrorReturnCode returns when application crashed at initialization stage.
	ErrorReturnCode = 1

	// SuccessReturnCode returns when application closed without panic.
	SuccessReturnCode = 0

	EnvPrefix = "FROSTFS_IR"
)

var (
	wg         = new(sync.WaitGroup)
	intErr     = make(chan error) // internal inner ring errors
	logPrm     = new(logger.Prm)
	innerRing  *innerring.Server
	pprofCmp   *pprofComponent
	metricsCmp *httpComponent
	log        *logger.Logger
	cfg        *viper.Viper
	configFile *string
	configDir  *string
	cmode      = &atomic.Bool{}
	audit      = &atomic.Bool{}
)

func exitErr(err error) {
	if err != nil {
		fmt.Println(err)
		os.Exit(ErrorReturnCode)
	}
}

func main() {
	configFile = flag.String("config", "", "path to config")
	configDir = flag.String("config-dir", "", "path to config directory")
	versionFlag := flag.Bool("version", false, "frostfs-ir node version")
	flag.Parse()

	if *versionFlag {
		fmt.Print(misc.BuildInfo("FrostFS Inner Ring node"))

		os.Exit(SuccessReturnCode)
	}

	var err error
	cfg, err = newConfig()
	exitErr(err)

	cmode.Store(cfg.GetBool("node.kludge_compatibility_mode"))

	metrics := irMetrics.NewInnerRingMetrics()

	err = logPrm.SetLevelString(
		cfg.GetString("logger.level"),
	)
	exitErr(err)
	err = logPrm.SetDestination(
		cfg.GetString("logger.destination"),
	)
	exitErr(err)
	logPrm.SamplingHook = metrics.LogMetrics().GetSamplingHook()
	logPrm.PrependTimestamp = cfg.GetBool("logger.timestamp")

	log, err = logger.NewLogger(logPrm)
	exitErr(err)

	ctx, cancel := context.WithCancel(context.Background())

	pprofCmp = newPprofComponent()
	pprofCmp.init(ctx)

	metricsCmp = newMetricsComponent()
	metricsCmp.init(ctx)
	audit.Store(cfg.GetBool("audit.enabled"))

	innerRing, err = innerring.New(ctx, log, cfg, intErr, metrics, cmode, audit)
	exitErr(err)

	pprofCmp.start(ctx)
	metricsCmp.start(ctx)

	// start inner ring
	err = innerRing.Start(ctx, intErr)
	exitErr(err)

	log.Info(ctx, logs.CommonApplicationStarted,
		zap.String("version", misc.Version))

	watchForSignal(ctx, cancel)

	<-ctx.Done() // graceful shutdown
	log.Debug(ctx, logs.FrostFSNodeWaitingForAllProcessesToStop)
	wg.Wait()

	log.Info(ctx, logs.FrostFSIRApplicationStopped)
}

func shutdown(ctx context.Context) {
	innerRing.Stop(ctx)
	if err := metricsCmp.shutdown(ctx); err != nil {
		log.Debug(ctx, logs.FrostFSIRCouldNotShutdownHTTPServer,
			zap.Error(err),
		)
	}
	if err := pprofCmp.shutdown(ctx); err != nil {
		log.Debug(ctx, logs.FrostFSIRCouldNotShutdownHTTPServer,
			zap.Error(err),
		)
	}

	if err := sdnotify.ClearStatus(); err != nil {
		log.Error(ctx, logs.FailedToReportStatusToSystemd, zap.Error(err))
	}
}