package main

import (
	"context"
	"flag"
	"fmt"
	"net/http"
	"os"
	"sync"

	"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
	"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
	httputil "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/http"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"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
	httpServers []*httputil.Server
	log         *logger.Logger
	cfg         *viper.Viper
	configFile  *string
	configDir   *string
)

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)

	err = logPrm.SetLevelString(
		cfg.GetString("logger.level"),
	)
	exitErr(err)

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

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

	initHTTPServers(cfg)

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

	// start HTTP servers
	for _, srv := range httpServers {
		wg.Add(1)
		go func(srv *httputil.Server) {
			exitErr(srv.Serve())
			wg.Done()
		}(srv)
	}

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

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

	watchForSignal(cancel)

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

	log.Info(logs.FrostFSIRApplicationStopped)
}

func initHTTPServers(cfg *viper.Viper) {
	items := []struct {
		cfgPrefix string
		handler   func() http.Handler
	}{
		{"pprof", httputil.Handler},
		{"prometheus", promhttp.Handler},
	}

	httpServers = make([]*httputil.Server, 0, len(items))

	for _, item := range items {
		if !cfg.GetBool(item.cfgPrefix + ".enabled") {
			log.Info(item.cfgPrefix + " is disabled, skip")
			continue
		}

		addr := cfg.GetString(item.cfgPrefix + ".address")

		var prm httputil.HTTPSrvPrm

		prm.Address = addr
		prm.Handler = item.handler()

		httpServers = append(httpServers,
			httputil.New(prm,
				httputil.WithShutdownTimeout(
					cfg.GetDuration(item.cfgPrefix+".shutdown_timeout"),
				),
			),
		)
	}
}

func shutdown() {
	innerRing.Stop()

	// shut down HTTP servers
	for _, srv := range httpServers {
		wg.Add(1)
		go func(srv *httputil.Server) {
			err := srv.Shutdown()
			if err != nil {
				log.Debug(logs.FrostFSIRCouldNotShutdownHTTPServer,
					zap.String("error", err.Error()),
				)
			}
			wg.Done()
		}(srv)
	}
}