package local_test

import (
	"context"
	"io/fs"
	"os"
	"path/filepath"
	"time"

	"github.com/navidrome/navidrome/conf"
	"github.com/navidrome/navidrome/core/storage"
	"github.com/navidrome/navidrome/core/storage/local"
	_ "github.com/navidrome/navidrome/core/storage/local"
	"github.com/navidrome/navidrome/model/metadata"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = XDescribe("Watcher", func() {
	var lsw storage.Watcher
	var tmpFolder string

	BeforeEach(func() {
		tmpFolder = GinkgoT().TempDir()

		local.RegisterExtractor("noop", func(fs fs.FS, path string) local.Extractor { return noopExtractor{} })
		conf.Server.Scanner.Extractor = "noop"

		ls, err := storage.For(tmpFolder)
		Expect(err).ToNot(HaveOccurred())

		// It should implement Watcher
		var ok bool
		lsw, ok = ls.(storage.Watcher)
		Expect(ok).To(BeTrue())

		// Make sure temp folder is created
		Eventually(func() error {
			_, err := os.Stat(tmpFolder)
			return err
		}).Should(Succeed())
	})

	It("should start and stop watcher", func() {
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()

		w, err := lsw.Start(ctx)
		Expect(err).ToNot(HaveOccurred())
		cancel()
		Eventually(w).Should(BeClosed())
	})

	It("should return error if watcher is already started", func() {
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()

		_, err := lsw.Start(ctx)
		Expect(err).ToNot(HaveOccurred())
		_, err = lsw.Start(ctx)
		Expect(err).To(HaveOccurred())
	})

	It("should detect new files", func() {
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()

		changes, err := lsw.Start(ctx)
		Expect(err).ToNot(HaveOccurred())

		_, err = os.Create(filepath.Join(tmpFolder, "test.txt"))
		Expect(err).ToNot(HaveOccurred())

		Eventually(changes).WithTimeout(2 * time.Second).Should(Receive(Equal(tmpFolder)))
	})

	It("should detect new subfolders", func() {
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()

		changes, err := lsw.Start(ctx)
		Expect(err).ToNot(HaveOccurred())

		Expect(os.Mkdir(filepath.Join(tmpFolder, "subfolder"), 0755)).To(Succeed())

		Eventually(changes).WithTimeout(2 * time.Second).Should(Receive(Equal(filepath.Join(tmpFolder, "subfolder"))))
	})

	It("should detect changes in subfolders recursively", func() {
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()

		subfolder := filepath.Join(tmpFolder, "subfolder1/subfolder2")
		Expect(os.MkdirAll(subfolder, 0755)).To(Succeed())

		changes, err := lsw.Start(ctx)
		Expect(err).ToNot(HaveOccurred())

		filePath := filepath.Join(subfolder, "test.txt")
		Expect(os.WriteFile(filePath, []byte("test"), 0600)).To(Succeed())

		Eventually(changes).WithTimeout(2 * time.Second).Should(Receive(Equal(filePath)))
	})

	It("should detect removed in files", func() {
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		changes, err := lsw.Start(ctx)
		Expect(err).ToNot(HaveOccurred())

		filePath := filepath.Join(tmpFolder, "test.txt")
		Expect(os.WriteFile(filePath, []byte("test"), 0600)).To(Succeed())

		Eventually(changes).WithTimeout(2 * time.Second).Should(Receive(Equal(filePath)))

		Expect(os.Remove(filePath)).To(Succeed())
		Eventually(changes).WithTimeout(2 * time.Second).Should(Receive(Equal(filePath)))
	})

	It("should detect file moves", func() {
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()

		filePath := filepath.Join(tmpFolder, "test.txt")
		Expect(os.WriteFile(filePath, []byte("test"), 0600)).To(Succeed())

		changes, err := lsw.Start(ctx)
		Expect(err).ToNot(HaveOccurred())

		newPath := filepath.Join(tmpFolder, "test2.txt")
		Expect(os.Rename(filePath, newPath)).To(Succeed())
		Eventually(changes).WithTimeout(2 * time.Second).Should(Receive(Equal(newPath)))
	})
})

type noopExtractor struct{}

func (s noopExtractor) Parse(files ...string) (map[string]metadata.Info, error) { return nil, nil }
func (s noopExtractor) Version() string                                         { return "0" }
