package config

import (
	"fmt"
	"path/filepath"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
	"go.uber.org/zap"
)

// ConfigWatcher surveille les fichiers de configuration pour changements (T0040)
type ConfigWatcher struct {
	watcher  *fsnotify.Watcher
	reloader *ConfigReloader
	logger   *zap.Logger
	stopChan chan struct{}
	stopOnce sync.Once // Ensures stopChan is closed only once
	wg       sync.WaitGroup
	debounce time.Duration
}

// NewConfigWatcher crée un nouveau watcher de configuration (T0040)
func NewConfigWatcher(reloader *ConfigReloader, logger *zap.Logger) (*ConfigWatcher, error) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, fmt.Errorf("failed to create watcher: %w", err)
	}

	return &ConfigWatcher{
		watcher:  watcher,
		reloader: reloader,
		logger:   logger,
		stopChan: make(chan struct{}),
		stopOnce: sync.Once{}, // Initialize sync.Once
		debounce: 500 * time.Millisecond,
	}, nil
}

// Watch surveille les fichiers .env pour changements (T0040)
func (w *ConfigWatcher) Watch(envFiles []string) error {
	// Ajouter les fichiers à surveiller
	for _, file := range envFiles {
		// Résoudre le chemin absolu pour éviter les problèmes de chemins relatifs
		absPath, err := filepath.Abs(file)
		if err != nil {
			w.logger.Warn("Failed to resolve absolute path", zap.String("file", file), zap.Error(err))
			absPath = file
		}

		if err := w.watcher.Add(absPath); err != nil {
			w.logger.Warn("Failed to watch file", zap.String("file", absPath), zap.Error(err))
			continue
		}
		w.logger.Info("Watching config file", zap.String("file", absPath))
	}

	w.wg.Add(1)
	go w.watchLoop()

	return nil
}

// watchLoop boucle principale de surveillance avec debouncing (T0040)
func (w *ConfigWatcher) watchLoop() {
	defer w.wg.Done()

	var debounceTimer *time.Timer

	for {
		select {
		case event, ok := <-w.watcher.Events:
			if !ok {
				return
			}

			// Ignorer les opérations autres que Write et Create
			if event.Op&fsnotify.Write == 0 && event.Op&fsnotify.Create == 0 {
				continue
			}

			w.logger.Debug("Config file changed", zap.String("file", event.Name), zap.String("op", event.Op.String()))

			// Arrêter le timer précédent si existant
			if debounceTimer != nil {
				debounceTimer.Stop()
			}

			// Démarrer un nouveau timer de debounce
			debounceTimer = time.NewTimer(w.debounce)

			// Goroutine pour attendre le debounce et relancer
			go func(fileName string) {
				<-debounceTimer.C
				w.logger.Info("Config file changed, reloading", zap.String("file", fileName))
				if err := w.reloader.ReloadAll(); err != nil {
					w.logger.Error("Failed to reload config", zap.Error(err))
				} else {
					w.logger.Info("Config reloaded successfully")
				}
			}(event.Name)

		case err, ok := <-w.watcher.Errors:
			if !ok {
				return
			}
			w.logger.Error("Watcher error", zap.Error(err))

		case <-w.stopChan:
			// Arrêter le timer si actif
			if debounceTimer != nil {
				debounceTimer.Stop()
			}
			return
		}
	}
}

// Stop arrête la surveillance proprement (T0040)
func (w *ConfigWatcher) Stop() error {
	w.stopOnce.Do(func() {
		close(w.stopChan)
	})
	err := w.watcher.Close()
	w.wg.Wait()
	return err
}

// GetWatchedFiles retourne la liste des fichiers surveillés (T0040)
func (w *ConfigWatcher) GetWatchedFiles() []string {
	if w.watcher == nil {
		return []string{}
	}
	return w.watcher.WatchList()
}
