package fileutil

import (
	"context"
	"sync"

	"github.com/rjeczalik/notify"
	"github.com/rs/zerolog"

	"github.com/pomerium/pomerium/internal/log"
	"github.com/pomerium/pomerium/internal/signal"
)

// A Watcher watches files for changes.
type Watcher struct {
	*signal.Signal
	mu        sync.Mutex
	filePaths map[string]chan notify.EventInfo
}

// NewWatcher creates a new Watcher.
func NewWatcher() *Watcher {
	return &Watcher{
		Signal:    signal.New(),
		filePaths: map[string]chan notify.EventInfo{},
	}
}

// Add adds a new watch.
func (watcher *Watcher) Add(filePath string) {
	watcher.mu.Lock()
	defer watcher.mu.Unlock()

	ctx := log.WithContext(context.TODO(), func(c zerolog.Context) zerolog.Context {
		return c.Str("watch_file", filePath)
	})

	// already watching
	if _, ok := watcher.filePaths[filePath]; ok {
		return
	}

	ch := make(chan notify.EventInfo, 1)
	go func() {
		for evt := range ch {
			log.Info(ctx).Str("event", evt.Event().String()).Msg("filemgr: detected file change")
			watcher.Signal.Broadcast(ctx)
		}
	}()
	err := notify.Watch(filePath, ch, notify.All)
	if err != nil {
		log.Error(ctx).Err(err).Msg("filemgr: error watching file path")
		notify.Stop(ch)
		close(ch)
		return
	}
	log.Debug(ctx).Msg("filemgr: watching file for changes")

	watcher.filePaths[filePath] = ch
}

// Clear removes all watches.
func (watcher *Watcher) Clear() {
	watcher.mu.Lock()
	defer watcher.mu.Unlock()

	for filePath, ch := range watcher.filePaths {
		notify.Stop(ch)
		close(ch)
		delete(watcher.filePaths, filePath)
	}
}