package watcher

import (
	"os"
	"path/filepath"
	"testing"
	"time"
)

func TestWatcherDetectsChange(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "test.json")
	if err := os.WriteFile(path, []byte(`{"version": 1}`), 0644); err != nil {
		t.Fatalf("failed to write test file: %v", err)
	}

	changeDetected := make(chan bool, 1)
	onChange := func() {
		select {
		case changeDetected <- true:
		default:
		}
	}

	fw := NewFileWatcher(path, 100*time.Millisecond, onChange)
	fw.Start()
	defer fw.Stop()

	// Wait for initial hash to be computed
	time.Sleep(200 * time.Millisecond)

	// Modify the file
	if err := os.WriteFile(path, []byte(`{"version": 2}`), 0644); err != nil {
		t.Fatalf("failed to modify test file: %v", err)
	}

	// Wait for change detection
	select {
	case detected := <-changeDetected:
		if !detected {
			t.Error("expected change detection, got false")
		}
	case <-time.After(2 * time.Second):
		t.Error("timeout waiting for file change detection")
	}
}

func TestWatcherNoChange(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "test.json")
	if err := os.WriteFile(path, []byte(`{"version": 1}`), 0644); err != nil {
		t.Fatalf("failed to write test file: %v", err)
	}

	// With periodic reconciliation, onChange will be called every period.
	// Count how many times it's called within the wait period.
	callCount := 0
	onChange := func() {
		callCount++
	}

	fw := NewFileWatcher(path, 100*time.Millisecond, onChange)
	fw.Start()
	defer fw.Stop()

	// Wait without modifying the file
	time.Sleep(350 * time.Millisecond)

	// onChange should have been called ~3 times (0s, 0.1s, 0.2s, 0.3s) for periodic reconciliation
	if callCount < 1 {
		t.Errorf("expected at least 1 periodic reconciliation call, got %d", callCount)
	}
}

func TestWatcherMultipleChanges(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "test.json")
	if err := os.WriteFile(path, []byte(`{"v": 1}`), 0644); err != nil {
		t.Fatalf("failed to write test file: %v", err)
	}

	changeCount := 0
	var mu chan struct{}

	onChange := func() {
		changeCount++
		if mu != nil {
			mu <- struct{}{}
		}
	}

	fw := NewFileWatcher(path, 50*time.Millisecond, onChange)
	fw.Start()
	defer fw.Stop()

	// Wait for initial hash
	time.Sleep(100 * time.Millisecond)

	// Make 3 changes
	for i := 2; i <= 4; i++ {
		if err := os.WriteFile(path, []byte(`{"v": `+string(rune('0'+i))+`}`), 0644); err != nil {
			t.Fatalf("failed to modify file: %v", err)
		}
		time.Sleep(150 * time.Millisecond)
	}

	if changeCount < 2 {
		t.Errorf("expected at least 2 change detections, got %d", changeCount)
	}
}

func TestWatcherStop(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "test.json")
	if err := os.WriteFile(path, []byte(`{"v": 1}`), 0644); err != nil {
		t.Fatalf("failed to write test file: %v", err)
	}

	fw := NewFileWatcher(path, 50*time.Millisecond, func() {})
	fw.Start()

	time.Sleep(100 * time.Millisecond)
	fw.Stop()

	// Should not panic on double stop or further operations
	time.Sleep(100 * time.Millisecond)
}

func TestWatcherNonexistentFile(t *testing.T) {
	onChange := func() {
		t.Error("onChange should not be called for nonexistent file")
	}

	fw := NewFileWatcher("/nonexistent/file.json", 100*time.Millisecond, onChange)
	fw.Start()
	defer fw.Stop()

	// Wait — should not call onChange since file doesn't exist
	time.Sleep(300 * time.Millisecond)
}

func TestWatcherReproducible(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "test.json")
	if err := os.WriteFile(path, []byte(`{"data": "value"}`), 0644); err != nil {
		t.Fatalf("failed to write test file: %v", err)
	}

	// Create two watchers on the same file
	var detected1, detected2 bool

	fw1 := NewFileWatcher(path, 50*time.Millisecond, func() { detected1 = true })
	fw2 := NewFileWatcher(path, 50*time.Millisecond, func() { detected2 = true })

	fw1.Start()
	fw2.Start()
	defer fw1.Stop()
	defer fw2.Stop()

	time.Sleep(100 * time.Millisecond)

	// Modify file
	if err := os.WriteFile(path, []byte(`{"data": "changed"}`), 0644); err != nil {
		t.Fatalf("failed to modify file: %v", err)
	}

	time.Sleep(300 * time.Millisecond)

	if !detected1 {
		t.Error("watcher 1 did not detect change")
	}
	if !detected2 {
		t.Error("watcher 2 did not detect change")
	}
}