package main

import (
	"context"
	"os"
	"strings"
	"syscall"
	"time"

	"github.com/h44z/wg-portal/internal/app/api/core"
	handlersV0 "github.com/h44z/wg-portal/internal/app/api/v0/handlers"
	backendV1 "github.com/h44z/wg-portal/internal/app/api/v1/backend"
	handlersV1 "github.com/h44z/wg-portal/internal/app/api/v1/handlers"
	"github.com/h44z/wg-portal/internal/app/audit"
	"github.com/h44z/wg-portal/internal/app/auth"
	"github.com/h44z/wg-portal/internal/app/configfile"
	"github.com/h44z/wg-portal/internal/app/mail"
	"github.com/h44z/wg-portal/internal/app/route"
	"github.com/h44z/wg-portal/internal/app/users"
	"github.com/h44z/wg-portal/internal/app/wireguard"

	"github.com/h44z/wg-portal/internal"
	"github.com/h44z/wg-portal/internal/adapters"
	"github.com/h44z/wg-portal/internal/app"
	"github.com/h44z/wg-portal/internal/config"
	"github.com/sirupsen/logrus"
	evbus "github.com/vardius/message-bus"
)

// main entry point for WireGuard Portal
func main() {
	ctx := internal.SignalAwareContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)

	logrus.Infof("Starting WireGuard Portal V2...")
	logrus.Infof("WireGuard Portal version: %s", internal.Version)

	cfg, err := config.GetConfig()
	internal.AssertNoError(err)
	setupLogging(cfg)

	cfg.LogStartupValues()

	rawDb, err := adapters.NewDatabase(cfg.Database)
	internal.AssertNoError(err)

	database, err := adapters.NewSqlRepository(rawDb)
	internal.AssertNoError(err)

	wireGuard := adapters.NewWireGuardRepository()

	wgQuick := adapters.NewWgQuickRepo()

	mailer := adapters.NewSmtpMailRepo(cfg.Mail)

	metricsServer := adapters.NewMetricsServer(cfg)

	cfgFileSystem, err := adapters.NewFileSystemRepository(cfg.Advanced.ConfigStoragePath)
	internal.AssertNoError(err)

	shouldExit, err := app.HandleProgramArgs(cfg, rawDb)
	switch {
	case shouldExit && err == nil:
		return
	case shouldExit && err != nil:
		logrus.Errorf("Failed to process program args: %v", err)
		os.Exit(1)
	case !shouldExit:
		internal.AssertNoError(err)
	}

	queueSize := 100
	eventBus := evbus.New(queueSize)

	userManager, err := users.NewUserManager(cfg, eventBus, database, database)
	internal.AssertNoError(err)

	authenticator, err := auth.NewAuthenticator(&cfg.Auth, cfg.Web.ExternalUrl, eventBus, userManager)
	internal.AssertNoError(err)

	wireGuardManager, err := wireguard.NewWireGuardManager(cfg, eventBus, wireGuard, wgQuick, database)
	internal.AssertNoError(err)

	statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, eventBus, database, wireGuard, metricsServer)
	internal.AssertNoError(err)

	cfgFileManager, err := configfile.NewConfigFileManager(cfg, eventBus, database, database, cfgFileSystem)
	internal.AssertNoError(err)

	mailManager, err := mail.NewMailManager(cfg, mailer, cfgFileManager, database, database)
	internal.AssertNoError(err)

	auditRecorder, err := audit.NewAuditRecorder(cfg, eventBus, database)
	internal.AssertNoError(err)
	auditRecorder.StartBackgroundJobs(ctx)

	routeManager, err := route.NewRouteManager(cfg, eventBus, database)
	internal.AssertNoError(err)
	routeManager.StartBackgroundJobs(ctx)

	backend, err := app.New(cfg, eventBus, authenticator, userManager, wireGuardManager,
		statisticsCollector, cfgFileManager, mailManager)
	internal.AssertNoError(err)
	err = backend.Startup(ctx)
	internal.AssertNoError(err)

	apiFrontend := handlersV0.NewRestApi(cfg, backend)

	apiV1BackendUsers := backendV1.NewUserService(cfg, userManager)
	apiV1BackendPeers := backendV1.NewPeerService(cfg, wireGuardManager, userManager)
	apiV1BackendInterfaces := backendV1.NewInterfaceService(cfg, wireGuardManager)
	apiV1BackendProvisioning := backendV1.NewProvisioningService(cfg, userManager, wireGuardManager, cfgFileManager)
	apiV1BackendMetrics := backendV1.NewMetricsService(cfg, database, userManager, wireGuardManager)
	apiV1EndpointUsers := handlersV1.NewUserEndpoint(apiV1BackendUsers)
	apiV1EndpointPeers := handlersV1.NewPeerEndpoint(apiV1BackendPeers)
	apiV1EndpointInterfaces := handlersV1.NewInterfaceEndpoint(apiV1BackendInterfaces)
	apiV1EndpointProvisioning := handlersV1.NewProvisioningEndpoint(apiV1BackendProvisioning)
	apiV1EndpointMetrics := handlersV1.NewMetricsEndpoint(apiV1BackendMetrics)

	apiV1 := handlersV1.NewRestApi(
		userManager,
		apiV1EndpointUsers,
		apiV1EndpointPeers,
		apiV1EndpointInterfaces,
		apiV1EndpointProvisioning,
		apiV1EndpointMetrics,
	)

	webSrv, err := core.NewServer(cfg, apiFrontend, apiV1)
	internal.AssertNoError(err)

	go metricsServer.Run(ctx)
	go webSrv.Run(ctx, cfg.Web.ListeningAddress)

	// wait until context gets cancelled
	<-ctx.Done()

	logrus.Infof("Stopping WireGuard Portal")

	time.Sleep(5 * time.Second) // wait for (most) goroutines to finish gracefully

	logrus.Infof("Stopped WireGuard Portal")
}

func setupLogging(cfg *config.Config) {
	switch strings.ToLower(cfg.Advanced.LogLevel) {
	case "trace":
		logrus.SetLevel(logrus.TraceLevel)
	case "debug":
		logrus.SetLevel(logrus.DebugLevel)
	case "info", "information":
		logrus.SetLevel(logrus.InfoLevel)
	case "warn", "warning":
		logrus.SetLevel(logrus.WarnLevel)
	case "error":
		logrus.SetLevel(logrus.ErrorLevel)
	default:
		logrus.SetLevel(logrus.InfoLevel)
	}

	switch {
	case cfg.Advanced.LogJson:
		logrus.SetFormatter(&logrus.JSONFormatter{
			PrettyPrint: cfg.Advanced.LogPretty,
		})
	case cfg.Advanced.LogPretty:
		logrus.SetFormatter(&logrus.TextFormatter{
			ForceColors:   true,
			DisableColors: false,
		})
	}
}
