package main

import (
	"context"
	"flag"
	"log/slog"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"

	"git.casaderoll.de/michael/urbm/internal/api"
	"git.casaderoll.de/michael/urbm/internal/notify"
	"git.casaderoll.de/michael/urbm/internal/platform"
	"git.casaderoll.de/michael/urbm/internal/restic"
	"git.casaderoll.de/michael/urbm/internal/rsync"
	"git.casaderoll.de/michael/urbm/internal/scheduler"
	"git.casaderoll.de/michael/urbm/internal/secrets"
	"git.casaderoll.de/michael/urbm/internal/service"
	"git.casaderoll.de/michael/urbm/internal/store"
)

var version = "dev"

func main() {
	configDir := flag.String("config-dir", "/boot/config/plugins/urbm", "persistent configuration directory")
	runtimeDir := flag.String("runtime-dir", "/run/urbm", "runtime directory")
	stateDir := flag.String("state-dir", "/var/lib/urbm", "volatile state directory")
	socket := flag.String("socket", "/run/urbm/urbm.sock", "Unix API socket")
	flag.Parse()

	log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
	if err := os.MkdirAll(*runtimeDir, 0750); err != nil {
		log.Error("create runtime directory", "error", err)
		os.Exit(1)
	}
	if err := os.MkdirAll(*stateDir, 0750); err != nil {
		log.Error("create state directory", "error", err)
		os.Exit(1)
	}

	configStore := store.New(*configDir)
	if err := configStore.Init(); err != nil {
		log.Error("initialize config store", "error", err)
		os.Exit(1)
	}
	secretStore := secrets.New(filepath.Join(*configDir, "secrets"))
	if err := secretStore.Init(); err != nil {
		log.Error("initialize secret store", "error", err)
		os.Exit(1)
	}

	config, err := configStore.LoadConfig()
	if err != nil {
		log.Error("load config", "error", err)
		os.Exit(1)
	}
	rr := &restic.Runner{Binary: config.Settings.ResticPath, RuntimeDir: *runtimeDir, Secrets: secretStore, Log: log}
	rs := &rsync.Runner{Binary: config.Settings.RsyncPath}
	mounts := &platform.MountManager{RuntimeDir: *runtimeDir, Secrets: secretStore}
	workloads := &platform.WorkloadManager{RuntimeDir: *runtimeDir}
	notifier := &notify.Sender{Secrets: secretStore}
	svc, err := service.New(configStore, secretStore, rr, rs, mounts, workloads, notifier, log)
	if err != nil {
		log.Error("initialize service", "error", err)
		os.Exit(1)
	}

	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer cancel()
	go svc.RunQueue(ctx)
	api.Version = version
	sched := scheduler.New(svc.Config, svc.EnqueueScheduled, svc.EnqueueMaintenance, svc.LastRun, log)
	go sched.Run(ctx)

	server := api.New(*socket, svc, log)
	go func() {
		if err := server.ListenAndServe(); err != nil {
			log.Error("api server stopped", "error", err)
			cancel()
		}
	}()
	log.Info("urbm daemon started", "socket", *socket)
	<-ctx.Done()
	svc.Stop()
	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer shutdownCancel()
	_ = server.Shutdown(shutdownCtx)
	log.Info("urbm daemon stopped")
}
