package wconfig

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"sync"

	"github.com/fsnotify/fsnotify"
	"github.com/wavetermdev/thenextwave/pkg/eventbus"
	"github.com/wavetermdev/thenextwave/pkg/wavebase"
)

const configDir = "config"

var configDirAbsPath = filepath.Join(wavebase.GetWaveHomeDir(), configDir)

var instance *Watcher
var once sync.Once

type Watcher struct {
	initialized         bool
	watcher             *fsnotify.Watcher
	mutex               sync.Mutex
	settingsFile        string
	getSettingsDefaults func() SettingsConfigType
	settingsData        SettingsConfigType
}

type WatcherUpdate struct {
	File   string             `json:"file"`
	Update SettingsConfigType `json:"update"`
	Error  string             `json:"error"`
}

func readFileContents(filePath string, getDefaults func() SettingsConfigType) (*SettingsConfigType, error) {
	if getDefaults == nil {
		log.Printf("oopsie")
		return nil, fmt.Errorf("watcher started without defaults")
	}
	content := getDefaults()
	data, err := os.ReadFile(filePath)
	if err != nil {
		log.Printf("doopsie: %v", err)
		return nil, err
	}
	if err := json.Unmarshal(data, &content); err != nil {
		return nil, err
	}
	return &content, nil
}

// GetWatcher returns the singleton instance of the Watcher
func GetWatcher() *Watcher {
	once.Do(func() {
		watcher, err := fsnotify.NewWatcher()
		if err != nil {
			log.Printf("failed to create file watcher: %v", err)
			return
		}
		os.MkdirAll(configDirAbsPath, 0751)
		instance = &Watcher{watcher: watcher}
		log.Printf("started config watcher: %v\n", configDirAbsPath)
		if err := instance.addSettingsFile(settingsAbsPath, getSettingsConfigDefaults); err != nil {
			log.Printf("failed to add path %s to watcher: %v", configDirAbsPath, err)
			return
		}
	})
	return instance
}

func (w *Watcher) addSettingsFile(filename string, getDefaults func() SettingsConfigType) error {
	w.mutex.Lock()
	defer w.mutex.Unlock()

	w.getSettingsDefaults = getDefaults
	filename = filepath.ToSlash(filename)

	stat, err := os.Lstat(filename)
	if err != nil {
		fmt.Printf("warning: cannot stat file: %v", err)
	} else {
		if stat.IsDir() {
			return fmt.Errorf("warning: can't watch directory instead of file: %v", err)
		}

	}
	w.settingsFile = filename
	w.watcher.Add(filepath.Dir(filename))

	return nil
}

func (w *Watcher) Start() {
	w.mutex.Lock()
	defer w.mutex.Unlock()

	log.Printf("starting file watcher\n")
	w.initialized = true
	w.sendInitialValues()

	go func() {

		for {
			select {
			case event, ok := <-w.watcher.Events:
				if !ok {
					return
				}
				w.handleEvent(event)
			case err, ok := <-w.watcher.Errors:
				if !ok {
					return
				}
				log.Println("watcher error:", err)
			}
		}
	}()
}

// for initial values, exit on first error
func (w *Watcher) sendInitialValues() error {
	filename := w.settingsFile

	content, err := readFileContents(w.settingsFile, w.getSettingsDefaults)
	if os.IsNotExist(err) || os.IsPermission(err) {
		log.Printf("settings file cannot be read: using defaults")
		defaults := w.getSettingsDefaults()
		content = &defaults
	} else if err != nil {
		return err
	}

	message := WatcherUpdate{
		File:   filename,
		Update: *content,
	}

	w.settingsData = message.Update

	log.Printf("watcher: initial values: %s -> %v", filename, content)
	return nil
}

func (w *Watcher) Close() {
	w.mutex.Lock()
	defer w.mutex.Unlock()
	if w.watcher != nil {
		w.watcher.Close()
		w.watcher = nil
		log.Println("file watcher closed")
	}
}

func (w *Watcher) broadcast(message WatcherUpdate) {
	// send to frontend
	eventbus.SendEvent(eventbus.WSEventType{
		EventType: "config",
		Data:      message,
	})

	if message.File == w.settingsFile {
		w.settingsData = message.Update
	}

	if message.Error != "" {
		log.Printf("watcher: error processing %s. sending defaults: %v", message.File, message.Error)
	} else {
		log.Printf("watcher: update: %s -> %v", message.File, message.Update)
	}
}

func (w *Watcher) GetSettingsConfig() SettingsConfigType {
	w.mutex.Lock()
	defer w.mutex.Unlock()

	return w.settingsData
}

func (w *Watcher) handleEvent(event fsnotify.Event) {
	w.mutex.Lock()
	defer w.mutex.Unlock()

	fileName := filepath.ToSlash(event.Name)

	// only consider events for the tracked files
	if fileName != w.settingsFile {
		return
	}
	defaults := w.getSettingsDefaults()

	if event.Op&fsnotify.Remove == fsnotify.Remove || event.Op&fsnotify.Rename == fsnotify.Rename {
		message := WatcherUpdate{
			File:   fileName,
			Update: defaults,
		}
		w.broadcast(message)
	}

	if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
		content, err := readFileContents(fileName, w.getSettingsDefaults)
		if err != nil {
			message := WatcherUpdate{
				File:   fileName,
				Update: defaults,
				Error:  err.Error(),
			}
			w.broadcast(message)
			return
		}
		message := WatcherUpdate{
			File:   fileName,
			Update: *content,
		}

		w.broadcast(message)
	}
}

func (w *Watcher) AddWidget(newWidget WidgetsConfigType) error {
	current := w.GetSettingsConfig()
	current.Widgets = append(current.Widgets, newWidget)
	update, err := json.Marshal(current)
	if err != nil {
		return err
	}

	os.MkdirAll(filepath.Dir(w.settingsFile), 0751)
	return os.WriteFile(w.settingsFile, update, 0644)
}

func (w *Watcher) RmWidget(idx uint) error {
	current := w.GetSettingsConfig().Widgets
	truncated := append(current[:idx], current[idx+1:]...)
	update, err := json.Marshal(truncated)
	if err != nil {
		return err
	}

	os.MkdirAll(filepath.Dir(w.settingsFile), 0751)
	return os.WriteFile(w.settingsFile, update, 0644)
}
