package app

import (
	"log/slog"
	"path/filepath"
	"testing"
	"time"

	"github.com/fsnotify/fsnotify"
)

func TestFileWatcherFiltersProcessableEvents(t *testing.T) {
	t.Parallel()

	root := t.TempDir()
	watcher := &FileWatcher{rootDir: root, logger: slog.Default()}

	tests := []struct {
		name string
		path string
		op   fsnotify.Op
		want bool
	}{
		{name: "markdown write", path: "guide.md", op: fsnotify.Write, want: true},
		{name: "markdown create", path: "nested/guide.md", op: fsnotify.Create, want: true},
		{name: "case insensitive markdown", path: "guide.MD", op: fsnotify.Write, want: true},
		{name: "markdown delete", path: "guide.md", op: fsnotify.Remove, want: true},
		{name: "non markdown write", path: "logo.webp", op: fsnotify.Write, want: false},
		{name: "non markdown delete", path: "logo.webp", op: fsnotify.Remove, want: false},
		{name: "hidden markdown file", path: ".guide.md", op: fsnotify.Write, want: false},
		{name: "hidden directory", path: ".cache/guide.md", op: fsnotify.Write, want: false},
		{name: "backup file", path: "guide.md~", op: fsnotify.Write, want: false},
		{name: "vim swap", path: "guide.md.swp", op: fsnotify.Write, want: false},
		{name: "emacs lock file", path: "#guide.md#", op: fsnotify.Write, want: false},
		{name: "chmod only", path: "guide.md", op: fsnotify.Chmod, want: false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			event := fsnotify.Event{Name: filepath.Join(root, tt.path), Op: tt.op}
			if got := watcher.isProcessableEvent(event); got != tt.want {
				t.Fatalf("isProcessableEvent() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestFileWatcherDebouncesPendingSyncs(t *testing.T) {
	t.Parallel()

	root := t.TempDir()
	watcher := &FileWatcher{
		rootDir:  root,
		logger:   slog.Default(),
		debounce: 10 * time.Millisecond,
	}

	var changed []string
	watcher.SetOnChange(func(path string) {
		changed = append(changed, path)
	})

	pending := make(map[string]struct{})
	var timer *time.Timer
	var timerC <-chan time.Time
	defer watcher.stopTimer(timer)

	watcher.scheduleSync(filepath.Join(root, "one.md"), pending, &timer, &timerC)
	watcher.scheduleSync(filepath.Join(root, "two.md"), pending, &timer, &timerC)

	select {
	case <-timerC:
		watcher.flushPending(pending)
	case <-time.After(250 * time.Millisecond):
		t.Fatal("debounce timer did not fire")
	}

	if len(changed) != 1 {
		t.Fatalf("onChange calls = %d, want 1", len(changed))
	}
	if len(pending) != 0 {
		t.Fatalf("pending changes = %d, want 0", len(pending))
	}
}
