package cli

import (
	"context"
	"os"
	"os/signal"
	"syscall"

	"github.com/fsnotify/fsnotify"
	"github.com/rs/zerolog/log"

	"zotregistry.io/zot/pkg/api"
	"zotregistry.io/zot/pkg/api/config"
)

type HotReloader struct {
	watcher  *fsnotify.Watcher
	filePath string
	ctlr     *api.Controller
}

func NewHotReloader(ctlr *api.Controller, filePath string) (*HotReloader, error) {
	// creates a new file watcher
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}

	hotReloader := &HotReloader{
		watcher:  watcher,
		filePath: filePath,
		ctlr:     ctlr,
	}

	return hotReloader, nil
}

func signalHandler(ctlr *api.Controller, sigCh chan os.Signal, ctx context.Context, cancel context.CancelFunc) {
	select {
	// if signal then shutdown
	case sig := <-sigCh:
		defer cancel()

		ctlr.Log.Info().Interface("signal", sig).Msg("received signal")

		// gracefully shutdown http server
		ctlr.Shutdown() //nolint: contextcheck

		close(sigCh)
	// if reload then return
	case <-ctx.Done():
		return
	}
}

func initShutDownRoutine(ctlr *api.Controller, ctx context.Context, cancel context.CancelFunc) {
	sigCh := make(chan os.Signal, 1)

	go signalHandler(ctlr, sigCh, ctx, cancel)

	// block all async signals to this server
	signal.Ignore()

	// handle SIGINT and SIGHUP.
	signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP)
}

func (hr *HotReloader) Start() context.Context {
	reloadCtx, cancelFunc := context.WithCancel(context.Background())

	done := make(chan bool)

	initShutDownRoutine(hr.ctlr, reloadCtx, cancelFunc)

	// run watcher
	go func() {
		defer hr.watcher.Close()

		go func() {
			for {
				select {
				// watch for events
				case event := <-hr.watcher.Events:
					if event.Op == fsnotify.Write {
						log.Info().Msg("config file changed, trying to reload config")

						newConfig := config.New()

						err := LoadConfiguration(newConfig, hr.filePath)
						if err != nil {
							log.Error().Err(err).Msg("couldn't reload config, retry writing it.")

							continue
						}
						// if valid config then reload
						cancelFunc()

						// create new context
						reloadCtx, cancelFunc = context.WithCancel(context.Background())

						// init shutdown routine
						initShutDownRoutine(hr.ctlr, reloadCtx, cancelFunc)

						hr.ctlr.LoadNewConfig(reloadCtx, newConfig)
					}
				// watch for errors
				case err := <-hr.watcher.Errors:
					log.Error().Err(err).Str("config", hr.filePath).Msg("fsnotfy error while watching config")
					panic(err)
				}
			}
		}()

		if err := hr.watcher.Add(hr.filePath); err != nil {
			log.Error().Err(err).Str("config", hr.filePath).Msg("error adding config file to FsNotify watcher")
			panic(err)
		}

		<-done
	}()

	return reloadCtx
}
