// Copyright 2024 schukai GmbH
// SPDX-License-Identifier: proprietary

package watch

import (
	"errors"
	"github.com/stretchr/testify/assert"
	"os"
	"testing"
	"time"
)

func TestRemoveByTagsAndWatchListByTags(t *testing.T) {
	l := NewLighthouse()
	l.SetDebounce(500 * time.Millisecond)

	tmpDir1 := t.TempDir()
	tmpDir2 := t.TempDir()
	tmpDir3 := t.TempDir()

	// Add a few watches
	err := l.Add(&Watch{Path: tmpDir1, Tags: []string{"tag1", "tag2"}})
	if err != nil {
		t.Errorf("Failed to add watch: %v", err)
	}

	err = l.Add(&Watch{Path: tmpDir2, Tags: []string{"tag1"}})
	if err != nil {
		t.Errorf("Failed to add watch: %v", err)
	}

	err = l.Add(&Watch{Path: tmpDir3, Tags: []string{"tag2"}})
	if err != nil {
		t.Errorf("Failed to add watch: %v", err)
	}

	// Test WatchListByTags
	list := l.WatchListByTags([]string{"tag1"})
	expectedList := []string{tmpDir1, tmpDir2}
	for i, path := range list {
		if path != expectedList[i] {
			t.Errorf("Expected %s, got %s", expectedList[i], path)
		}
	}

	// Test RemoveByTags
	err = l.RemoveByTags([]string{"tag1"})
	if err != nil {
		t.Errorf("Failed to remove by tags: %v", err)
	}

	// Confirm removal
	if l.IsWatched(tmpDir1) || l.IsWatched(tmpDir2) {
		t.Errorf("RemoveByTags did not remove the watches correctly")
	}

	// Confirm remaining watch
	if !l.IsWatched(tmpDir3) {
		t.Errorf("RemoveByTags removed an unrelated watch")
	}
}

func TestAddWatch(t *testing.T) {
	tempDir, err := os.MkdirTemp("", "watchtest")
	assert.Nil(t, err)
	defer func() {
		_ = os.RemoveAll(tempDir) // Cleanup
	}()

	l := NewLighthouse()

	w := &Watch{Path: tempDir}
	err = l.Add(w)
	assert.Nil(t, err)

	err = l.Add(w)
	assert.True(t, errors.As(err, &AlreadyWatchedPathError{}))
}

func TestRemoveWatch(t *testing.T) {
	tempDir, err := os.MkdirTemp("", "watchtest")
	assert.Nil(t, err)
	defer func() {
		_ = os.RemoveAll(tempDir) // Cleanup
	}()

	l := NewLighthouse()

	w := &Watch{Path: tempDir}
	err = l.Add(w)
	assert.Nil(t, err)

	err = l.Remove(tempDir)
	assert.Nil(t, err)

	err = l.Remove("path/not/watched")
	assert.True(t, errors.As(err, &UnwatchedPathError{}))
}

func TestNewLighthouse(t *testing.T) {
	l := NewLighthouse()
	assert.NotNil(t, l)
	assert.True(t, l.IsActive())
}

func TestIsWatched(t *testing.T) {
	tempDir, err := os.MkdirTemp("", "watchtest")
	assert.Nil(t, err)
	defer func() {
		_ = os.RemoveAll(tempDir) // Cleanup
	}()

	l := NewLighthouse()

	w := &Watch{Path: tempDir}
	err = l.Add(w)
	assert.Nil(t, err)

	assert.True(t, l.IsWatched(tempDir))
	assert.False(t, l.IsWatched("path/not/watched"))
}

func TestIsActiveWatched(t *testing.T) {
	tempDir, err := os.MkdirTemp("", "watchtest")
	assert.Nil(t, err)
	defer func() {
		_ = os.RemoveAll(tempDir) // Cleanup
	}()

	l := NewLighthouse()

	w := &Watch{Path: tempDir}
	err = l.Add(w)
	assert.Nil(t, err)

	assert.True(t, l.IsActiveWatched(tempDir))
	assert.False(t, l.IsActiveWatched("path/not/watched"))
}

func TestWatchList(t *testing.T) {
	tempDir, err := os.MkdirTemp("", "watchtest")
	assert.Nil(t, err)
	defer func() {
		_ = os.RemoveAll(tempDir) // Cleanup
	}()

	l := NewLighthouse()

	w := &Watch{Path: tempDir}
	err = l.Add(w)
	assert.Nil(t, err)

	assert.Equal(t, 1, len(l.WatchList()))
	assert.Equal(t, tempDir, l.WatchList()[0])
}

// Test IsInSync with a file that is not watched by fsnotify
func TestIsInSync(t *testing.T) {
	tempDir, err := os.MkdirTemp("", "watchtest")
	assert.Nil(t, err)

	tempFile, err := os.CreateTemp(tempDir, "test")
	assert.Nil(t, err)

	defer func() {
		_ = os.RemoveAll(tempDir)         // Cleanup
		_ = os.RemoveAll(tempFile.Name()) // Cleanup
	}()

	l := NewLighthouse()

	w := &Watch{Path: tempDir}
	err = l.Add(w)
	assert.Nil(t, err)

	w2 := &Watch{Path: tempFile.Name()}
	err = l.Add(w2)
	assert.Nil(t, err)

	err = l.Sync()
	assert.Nil(t, err)

	assert.True(t, l.IsInSync())

	internal := l.(*lighthouse)
	internal.fsnotify.Remove(tempFile.Name())

	assert.False(t, l.IsInSync())

}

// test Sync
func TestSync(t *testing.T) {
	tempDir, err := os.MkdirTemp("", "watchtest")
	assert.Nil(t, err)

	tempFile, err := os.CreateTemp(tempDir, "test")
	assert.Nil(t, err)

	defer func() {
		_ = os.RemoveAll(tempDir)         // Cleanup
		_ = os.RemoveAll(tempFile.Name()) // Cleanup
	}()

	l := NewLighthouse()

	w := &Watch{Path: tempDir}
	err = l.Add(w)
	assert.Nil(t, err)

	w2 := &Watch{Path: tempFile.Name()}
	err = l.Add(w2)
	assert.Nil(t, err)

	err = l.Sync()
	assert.Nil(t, err)
}