package infra

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

func TestWatchDirFsnotify(t *testing.T) {
	t.Run("detecta escritura de archivo", func(t *testing.T) {
		tmpDir := t.TempDir()

		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()

		ch, err := WatchDirFsnotify(ctx, tmpDir)
		if err != nil {
			t.Fatalf("WatchDirFsnotify: %v", err)
		}

		// Dar tiempo al watcher para arrancar
		time.Sleep(50 * time.Millisecond)

		// Escribir un archivo
		testFile := filepath.Join(tmpDir, "test.md")
		if err := os.WriteFile(testFile, []byte("hello"), 0644); err != nil {
			t.Fatalf("WriteFile: %v", err)
		}

		// Esperar evento (debounce 200ms + margen)
		select {
		case ev, ok := <-ch:
			if !ok {
				t.Fatal("channel closed unexpectedly")
			}
			if ev.Path != testFile {
				t.Errorf("Path: got %q, want %q", ev.Path, testFile)
			}
			if ev.Op != "create" && ev.Op != "write" {
				t.Errorf("Op: got %q, want 'create' or 'write'", ev.Op)
			}
		case <-ctx.Done():
			t.Fatal("timeout waiting for fs event")
		}
	})

	t.Run("canal se cierra cuando ctx cancela", func(t *testing.T) {
		tmpDir := t.TempDir()

		ctx, cancel := context.WithCancel(context.Background())

		ch, err := WatchDirFsnotify(ctx, tmpDir)
		if err != nil {
			t.Fatalf("WatchDirFsnotify: %v", err)
		}

		// Cancelar inmediatamente
		cancel()

		// El canal debe cerrarse
		timeout := time.After(2 * time.Second)
		// Drenar cualquier evento pendiente hasta que el canal se cierre
		for {
			select {
			case _, ok := <-ch:
				if !ok {
					return // canal cerrado correctamente
				}
			case <-timeout:
				t.Fatal("channel not closed after ctx cancel within 2s")
			}
		}
	})

	t.Run("error en directorio inexistente", func(t *testing.T) {
		ctx := context.Background()
		_, err := WatchDirFsnotify(ctx, "/nonexistent/dir/that/does/not/exist")
		if err == nil {
			t.Error("expected error for nonexistent directory")
		}
	})

	t.Run("debounce agrupa multiples escrituras", func(t *testing.T) {
		tmpDir := t.TempDir()

		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()

		ch, err := WatchDirFsnotify(ctx, tmpDir)
		if err != nil {
			t.Fatalf("WatchDirFsnotify: %v", err)
		}

		time.Sleep(50 * time.Millisecond)

		testFile := filepath.Join(tmpDir, "debounce.md")
		// Escribir 5 veces rapidamente
		for i := 0; i < 5; i++ {
			_ = os.WriteFile(testFile, []byte("content"), 0644)
			time.Sleep(10 * time.Millisecond)
		}

		// Esperar debounce + margen
		time.Sleep(400 * time.Millisecond)

		// Debe haber llegado al menos un evento pero no 5
		eventCount := 0
		drain:
		for {
			select {
			case _, ok := <-ch:
				if !ok {
					break drain
				}
				eventCount++
			default:
				break drain
			}
		}
		if eventCount == 0 {
			t.Error("expected at least one debounced event")
		}
		if eventCount >= 5 {
			t.Errorf("debounce failed: got %d events, expected fewer than 5", eventCount)
		}
	})
}
