// Copyright (C) 2022 Marius Schellenberger

package main

import (
	"context"
	"flag"
	"net/http"

	"os"
	"os/signal"
	"syscall"
	"time"

	"cachefs/pkg/config"
	"cachefs/pkg/fs"
	"cachefs/pkg/provider/parse"
	"cachefs/pkg/srv"

	"git.giftfish.de/ston1th/godrop/v2"
	"github.com/go-logr/logr"
	"k8s.io/klog/v2"
	"k8s.io/klog/v2/klogr"
)

var (
	version string
	cfg     string

	log logr.Logger
)

func main() {
	klog.InitFlags(nil)
	flag.StringVar(&cfg, "config", "", "path to config file")
	flag.Parse()

	log = klogr.New().WithName("main")
	log.Info("starting cachefs", "version", version)

	c, err := config.ParseFile(cfg)
	if err != nil {
		klog.Fatalf("init failed: %s", err)
	}

	err = dropPrivs(c)
	if err != nil {
		klog.Fatalf("init failed: %s", err)
	}

	if fs.SetBufferSize(c.Cache.Buffer) {
		log.V(2).Info("changed preload buffer size", "size", c.Cache.Buffer)
	}

	filesystem, err := fs.NewFSFromConfig(c, klogr.New().WithName("fs"))
	if err != nil {
		klog.Fatalf("init failed: %s", err)
	}
	slog := klogr.New().WithName("srv")
	s := &http.Server{
		Addr: c.Server.HTTP.Addr,
		Handler: srv.NewFileServer(
			filesystem,
			slog,
		),
	}
	go func() {
		log.Info("starting main server", "addr", c.Server.HTTP.Addr)
		err := s.ListenAndServe()
		if err != nil && err != http.ErrServerClosed {
			klog.Fatalf("init failed: %s", err)
		}
	}()
	var (
		dav   *http.Server
		cache *http.Server
		plain *http.Server
	)
	davAddr := c.Server.WebDav.Addr
	if davAddr != "" {
		log.Info("starting webdav server", "addr", davAddr)
		dav = &http.Server{
			Addr: davAddr,
			Handler: srv.NewWebDavServer(
				fs.NewWebDavFS(filesystem),
				slog.WithName("webdav"),
			),
		}
		go func() {
			err := dav.ListenAndServe()
			if err != nil && err != http.ErrServerClosed {
				klog.Fatalf("init failed: %s", err)
			}
		}()
	}
	cacheAddr := c.Server.Cache.Addr
	if cacheAddr != "" {
		log.Info("starting cache server", "addr", cacheAddr)
		cache = &http.Server{
			Addr: cacheAddr,
			Handler: srv.NewCacheServer(
				filesystem,
				slog.WithName("cache"),
			),
		}
		go func() {
			err := cache.ListenAndServe()
			if err != nil && err != http.ErrServerClosed {
				klog.Fatalf("init failed: %s", err)
			}
		}()
	}
	plainAddr := c.Server.Plain.Addr
	if plainAddr != "" {
		log.Info("starting plain server", "addr", plainAddr)
		plain = &http.Server{
			Addr: plainAddr,
			Handler: srv.NewPlainServer(
				filesystem,
				slog.WithName("plain"),
			),
		}
		go func() {
			err := plain.ListenAndServe()
			if err != nil && err != http.ErrServerClosed {
				klog.Fatalf("init failed: %s", err)
			}
		}()
	}
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
	<-sigs
	log.Info("cachefs shutdown initiated")
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	s.Shutdown(ctx)
	cancel()
	if dav != nil {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
		dav.Shutdown(ctx)
		cancel()
	}
	if cache != nil {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
		cache.Shutdown(ctx)
		cancel()
	}
	if plain != nil {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
		plain.Shutdown(ctx)
		cancel()
	}
	filesystem.Close()
	<-filesystem.Done()
	log.Info("cachefs shutdown completed")
	os.Exit(0)
}

func dropPrivs(cfg *config.Config) (err error) {
	// flock fattr
	err = godrop.PledgePromises("stdio rpath wpath cpath inet dns unveil")
	if err != nil {
		return
	}
	err = godrop.Unveil(cfg.Cache.Metadata, "rwc")
	if err != nil {
		return
	}
	for _, p := range parse.Paths(cfg.Cache.Src.Path) {
		err = godrop.Unveil(p, "r")
		if err != nil {
			return
		}
	}
	for i, p := range parse.Paths(cfg.Cache.Dst.Path) {
		if i == 0 {
			err = godrop.Unveil(p, "rwc")
		} else {
			err = godrop.Unveil(p, "r")
		}
		if err != nil {
			return
		}
	}
	return godrop.UnveilBlock()
}
