package actions

import (
	"context"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"time"

	"git.t-juice.club/torjus/gpaste"
	"git.t-juice.club/torjus/gpaste/api"
	"github.com/urfave/cli/v2"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func ActionServe(c *cli.Context) error {
	configPath := "gpaste-server.toml"
	if c.IsSet("config") {
		configPath = c.String("config")
	}

	f, err := os.Open(configPath)
	if err != nil {
		return cli.Exit(err, 1)
	}
	defer f.Close()
	cfg, err := gpaste.ServerConfigFromReader(f)
	if err != nil {
		return cli.Exit(err, 1)
	}
	// Setup loggers
	rootLogger := getRootLogger(cfg.LogLevel)
	serverLogger := rootLogger.Named("SERV")
	accessLogger := rootLogger.Named("ACCS")

	// Setup contexts for clean shutdown
	rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt)
	defer rootCancel()
	httpCtx, httpCancel := context.WithCancel(rootCtx)
	defer httpCancel()
	httpShutdownCtx, httpShutdownCancel := context.WithCancel(context.Background())
	defer httpShutdownCancel()

	go func() {
		srv := api.NewHTTPServer(cfg)
		srv.Addr = cfg.ListenAddr
		srv.Logger = serverLogger
		srv.AccessLogger = accessLogger

		// Wait for cancel
		go func() {
			<-httpCtx.Done()
			timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
			defer cancel()
			srv.Shutdown(timeoutCtx)
		}()
		serverLogger.Infow("Starting HTTP server.", "addr", cfg.ListenAddr)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			serverLogger.Errorw("Error during shutdown.", "error", err)
		}
		serverLogger.Infow("HTTP server shutdown complete.", "addr", cfg.ListenAddr)
		httpShutdownCancel()
	}()
	<-httpShutdownCtx.Done()

	return nil
}

func getRootLogger(level string) *zap.SugaredLogger {
	logEncoderConfig := zap.NewProductionEncoderConfig()
	logEncoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
	logEncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
	logEncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	logEncoderConfig.EncodeDuration = zapcore.StringDurationEncoder

	rootLoggerConfig := &zap.Config{
		Level:            zap.NewAtomicLevelAt(zap.DebugLevel),
		OutputPaths:      []string{"stdout"},
		ErrorOutputPaths: []string{"stdout"},
		Encoding:         "console",
		EncoderConfig:    logEncoderConfig,
		DisableCaller:    true,
	}

	switch strings.ToUpper(level) {
	case "DEBUG":
		rootLoggerConfig.DisableCaller = false
		rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
	case "INFO":
		rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
	case "WARN", "WARNING":
		rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.WarnLevel)
	case "ERR", "ERROR":
		rootLoggerConfig.Level = zap.NewAtomicLevelAt(zap.ErrorLevel)
	}

	rootLogger, err := rootLoggerConfig.Build()
	if err != nil {
		panic(err)
	}

	return rootLogger.Sugar()
}