package config

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

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	"go.uber.org/zap/zaptest"
)

func TestNewConfigWatcher(t *testing.T) {
	logger := zaptest.NewLogger(t)
	config := &Config{LogLevel: "INFO"}
	reloader := NewConfigReloader(config, logger)

	watcher, err := NewConfigWatcher(reloader, logger)
	require.NoError(t, err)
	assert.NotNil(t, watcher)
	defer watcher.Stop()
}

func TestConfigWatcher_Watch(t *testing.T) {
	logger := zaptest.NewLogger(t)
	config := &Config{LogLevel: "INFO"}
	reloader := NewConfigReloader(config, logger)

	watcher, err := NewConfigWatcher(reloader, logger)
	require.NoError(t, err)
	defer watcher.Stop()

	// Créer un fichier temporaire
	tmpDir := t.TempDir()
	tmpFile := filepath.Join(tmpDir, ".env.test")
	err = os.WriteFile(tmpFile, []byte("LOG_LEVEL=DEBUG\n"), 0644)
	require.NoError(t, err)

	err = watcher.Watch([]string{tmpFile})
	require.NoError(t, err)

	// Vérifier que le fichier est surveillé
	watchedFiles := watcher.GetWatchedFiles()
	assert.Contains(t, watchedFiles, tmpFile)
}

func TestConfigWatcher_Stop(t *testing.T) {
	logger := zaptest.NewLogger(t)
	config := &Config{LogLevel: "INFO"}
	reloader := NewConfigReloader(config, logger)

	watcher, err := NewConfigWatcher(reloader, logger)
	require.NoError(t, err)

	err = watcher.Stop()
	assert.NoError(t, err)
}

func TestConfigWatcher_GetWatchedFiles(t *testing.T) {
	logger := zaptest.NewLogger(t)
	config := &Config{LogLevel: "INFO"}
	reloader := NewConfigReloader(config, logger)

	watcher, err := NewConfigWatcher(reloader, logger)
	require.NoError(t, err)
	defer watcher.Stop()

	// Aucun fichier surveillé initialement
	files := watcher.GetWatchedFiles()
	assert.Empty(t, files)

	// Ajouter un fichier
	tmpDir := t.TempDir()
	tmpFile := filepath.Join(tmpDir, ".env.test")
	err = os.WriteFile(tmpFile, []byte("LOG_LEVEL=DEBUG\n"), 0644)
	require.NoError(t, err)

	err = watcher.Watch([]string{tmpFile})
	require.NoError(t, err)

	files = watcher.GetWatchedFiles()
	assert.Contains(t, files, tmpFile)
}

func TestConfigWatcher_MultipleFiles(t *testing.T) {
	logger := zaptest.NewLogger(t)
	config := &Config{LogLevel: "INFO"}
	reloader := NewConfigReloader(config, logger)

	watcher, err := NewConfigWatcher(reloader, logger)
	require.NoError(t, err)
	defer watcher.Stop()

	tmpDir := t.TempDir()
	file1 := filepath.Join(tmpDir, ".env")
	file2 := filepath.Join(tmpDir, ".env.production")

	err = os.WriteFile(file1, []byte("LOG_LEVEL=DEBUG\n"), 0644)
	require.NoError(t, err)

	err = os.WriteFile(file2, []byte("LOG_LEVEL=ERROR\n"), 0644)
	require.NoError(t, err)

	err = watcher.Watch([]string{file1, file2})
	require.NoError(t, err)

	watchedFiles := watcher.GetWatchedFiles()
	assert.Contains(t, watchedFiles, file1)
	assert.Contains(t, watchedFiles, file2)
}

func TestConfigWatcher_InvalidFile(t *testing.T) {
	logger := zaptest.NewLogger(t)
	config := &Config{LogLevel: "INFO"}
	reloader := NewConfigReloader(config, logger)

	watcher, err := NewConfigWatcher(reloader, logger)
	require.NoError(t, err)
	defer watcher.Stop()

	// Essayer de surveiller un fichier inexistant
	// Ne devrait pas planter, juste logger un avertissement
	err = watcher.Watch([]string{"/nonexistent/file.env"})
	// Le watch peut échouer mais ne doit pas planter
	if err != nil {
		t.Logf("Expected error for nonexistent file: %v", err)
	}
}

func TestConfigWatcher_FileChangeDetection(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping test that requires file watching in short mode")
	}

	logger := zaptest.NewLogger(t)
	config := &Config{LogLevel: "INFO"}
	reloader := NewConfigReloader(config, logger)

	watcher, err := NewConfigWatcher(reloader, logger)
	require.NoError(t, err)
	defer watcher.Stop()

	// Créer un fichier temporaire
	tmpDir := t.TempDir()
	tmpFile := filepath.Join(tmpDir, ".env.test")
	err = os.WriteFile(tmpFile, []byte("LOG_LEVEL=DEBUG\n"), 0644)
	require.NoError(t, err)

	err = watcher.Watch([]string{tmpFile})
	require.NoError(t, err)

	// Attendre que le watcher soit prêt
	time.Sleep(100 * time.Millisecond)

	// Modifier le fichier
	err = os.WriteFile(tmpFile, []byte("LOG_LEVEL=ERROR\n"), 0644)
	require.NoError(t, err)

	// Attendre le debounce + reload (500ms debounce + marge)
	time.Sleep(700 * time.Millisecond)

	// Le reload devrait avoir été déclenché
	// Note: Le reload peut ne pas avoir modifié config.LogLevel si le fichier .env
	// n'est pas chargé par LoadEnvFiles, mais on vérifie au moins que le watcher
	// a détecté le changement
	watchedFiles := watcher.GetWatchedFiles()
	assert.Contains(t, watchedFiles, tmpFile)
}

func TestNewConfigWatcher_Error(t *testing.T) {
	// Test avec un logger invalide ne devrait pas causer d'erreur
	// mais si fsnotify.NewWatcher() échoue, on devrait avoir une erreur
	// Dans la pratique, cette fonction ne devrait pas échouer sur la plupart des systèmes
	logger := zap.NewNop()
	config := &Config{LogLevel: "INFO"}
	reloader := NewConfigReloader(config, logger)

	watcher, err := NewConfigWatcher(reloader, logger)
	// Sur la plupart des systèmes, cela ne devrait pas échouer
	if err != nil {
		t.Logf("NewConfigWatcher failed (may be expected on some systems): %v", err)
	} else {
		require.NotNil(t, watcher)
		watcher.Stop()
	}
}

func TestConfigWatcher_StopMultipleTimes(t *testing.T) {
	logger := zaptest.NewLogger(t)
	config := &Config{LogLevel: "INFO"}
	reloader := NewConfigReloader(config, logger)

	watcher, err := NewConfigWatcher(reloader, logger)
	require.NoError(t, err)

	// Arrêter plusieurs fois ne devrait pas planter
	err = watcher.Stop()
	assert.NoError(t, err)

	// Essayer d'arrêter à nouveau
	err = watcher.Stop()
	// Peut retourner une erreur mais ne doit pas planter
	if err != nil {
		t.Logf("Second Stop() returned error (may be expected): %v", err)
	}
}

func TestConfigWatcher_EmptyFileList(t *testing.T) {
	logger := zaptest.NewLogger(t)
	config := &Config{LogLevel: "INFO"}
	reloader := NewConfigReloader(config, logger)

	watcher, err := NewConfigWatcher(reloader, logger)
	require.NoError(t, err)
	defer watcher.Stop()

	// Surveiller une liste vide
	err = watcher.Watch([]string{})
	assert.NoError(t, err)

	files := watcher.GetWatchedFiles()
	assert.Empty(t, files)
}

func TestConfigWatcher_RelativePath(t *testing.T) {
	logger := zaptest.NewLogger(t)
	config := &Config{LogLevel: "INFO"}
	reloader := NewConfigReloader(config, logger)

	watcher, err := NewConfigWatcher(reloader, logger)
	require.NoError(t, err)
	defer watcher.Stop()

	tmpDir := t.TempDir()
	// Créer le fichier
	absFile := filepath.Join(tmpDir, ".env.test")
	err = os.WriteFile(absFile, []byte("LOG_LEVEL=DEBUG\n"), 0644)
	require.NoError(t, err)

	// Changer vers le répertoire temporaire
	oldDir, err := os.Getwd()
	require.NoError(t, err)
	defer os.Chdir(oldDir)

	err = os.Chdir(tmpDir)
	require.NoError(t, err)

	// Essayer de surveiller avec un chemin relatif
	err = watcher.Watch([]string{".env.test"})
	require.NoError(t, err)

	// Vérifier que le chemin absolu est surveillé
	watchedFiles := watcher.GetWatchedFiles()
	assert.NotEmpty(t, watchedFiles)
	// Le chemin absolu devrait être dans la liste
	found := false
	for _, file := range watchedFiles {
		if filepath.Base(file) == ".env.test" {
			found = true
			break
		}
	}
	assert.True(t, found, "Relative path should be converted to absolute path")
}
