package persistence

import (
	"context"

	"github.com/Masterminds/squirrel"
	"github.com/deluan/rest"
	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/model/request"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("Annotation Filters", func() {
	var (
		albumRepo              *albumRepository
		albumWithoutAnnotation model.Album
	)

	BeforeEach(func() {
		ctx := request.WithUser(context.Background(), model.User{ID: "userid", UserName: "johndoe"})
		albumRepo = NewAlbumRepository(ctx, GetDBXBuilder()).(*albumRepository)

		// Create album without any annotation (no star, no rating)
		albumWithoutAnnotation = model.Album{ID: "no-annotation-album", Name: "No Annotation", LibraryID: 1}
		Expect(albumRepo.Put(&albumWithoutAnnotation)).To(Succeed())
	})

	AfterEach(func() {
		_, _ = albumRepo.executeSQL(squirrel.Delete("album").Where(squirrel.Eq{"id": albumWithoutAnnotation.ID}))
	})

	Describe("annotationBoolFilter", func() {
		DescribeTable("creates correct SQL expressions",
			func(field, value string, expectedSQL string, expectedArgs []any) {
				sqlizer := annotationBoolFilter(field)(field, value)
				sql, args, err := sqlizer.ToSql()
				Expect(err).ToNot(HaveOccurred())
				Expect(sql).To(Equal(expectedSQL))
				Expect(args).To(Equal(expectedArgs))
			},
			Entry("starred=true", "starred", "true", "COALESCE(starred, 0) > 0", []any(nil)),
			Entry("starred=false", "starred", "false", "COALESCE(starred, 0) = 0", []any(nil)),
			Entry("starred=True (case insensitive)", "starred", "True", "COALESCE(starred, 0) > 0", []any(nil)),
			Entry("rating=true", "rating", "true", "COALESCE(rating, 0) > 0", []any(nil)),
		)

		It("returns nil if value is not a string", func() {
			sqlizer := annotationBoolFilter("starred")("starred", 123)
			Expect(sqlizer).To(BeNil())
		})
	})

	Describe("starredFilter", func() {
		It("false includes items without annotations", func() {
			albums, err := albumRepo.GetAll(model.QueryOptions{
				Filters: annotationBoolFilter("starred")("starred", "false"),
			})
			Expect(err).ToNot(HaveOccurred())

			var found bool
			for _, a := range albums {
				if a.ID == albumWithoutAnnotation.ID {
					found = true
					break
				}
			}
			Expect(found).To(BeTrue(), "Item without annotation should be included in starred=false filter")
		})

		It("true excludes items without annotations", func() {
			albums, err := albumRepo.GetAll(model.QueryOptions{
				Filters: annotationBoolFilter("starred")("starred", "true"),
			})
			Expect(err).ToNot(HaveOccurred())

			for _, a := range albums {
				Expect(a.ID).ToNot(Equal(albumWithoutAnnotation.ID))
			}
		})
	})

	Describe("hasRatingFilter", func() {
		It("false includes items without annotations", func() {
			albums, err := albumRepo.GetAll(model.QueryOptions{
				Filters: annotationBoolFilter("rating")("rating", "false"),
			})
			Expect(err).ToNot(HaveOccurred())

			var found bool
			for _, a := range albums {
				if a.ID == albumWithoutAnnotation.ID {
					found = true
					break
				}
			}
			Expect(found).To(BeTrue(), "Item without annotation should be included in has_rating=false filter")
		})

		It("true excludes items without annotations", func() {
			albums, err := albumRepo.GetAll(model.QueryOptions{
				Filters: annotationBoolFilter("rating")("rating", "true"),
			})
			Expect(err).ToNot(HaveOccurred())

			for _, a := range albums {
				Expect(a.ID).ToNot(Equal(albumWithoutAnnotation.ID))
			}
		})

		It("true includes items with rating > 0", func() {
			// Create album with rating 1
			ratedAlbum := model.Album{ID: "rated-album", Name: "Rated Album", LibraryID: 1}
			Expect(albumRepo.Put(&ratedAlbum)).To(Succeed())
			Expect(albumRepo.SetRating(1, ratedAlbum.ID)).To(Succeed())
			defer func() {
				_, _ = albumRepo.executeSQL(squirrel.Delete("annotation").Where(squirrel.Eq{"item_id": ratedAlbum.ID}))
				_, _ = albumRepo.executeSQL(squirrel.Delete("album").Where(squirrel.Eq{"id": ratedAlbum.ID}))
			}()

			albums, err := albumRepo.GetAll(model.QueryOptions{
				Filters: annotationBoolFilter("rating")("rating", "true"),
			})
			Expect(err).ToNot(HaveOccurred())

			var found bool
			for _, a := range albums {
				if a.ID == ratedAlbum.ID {
					found = true
					break
				}
			}
			Expect(found).To(BeTrue(), "Album with rating 5 should be included in has_rating=true filter")
		})
	})

	It("ignores invalid filter values (not strings)", func() {
		res, err := albumRepo.ReadAll(rest.QueryOptions{
			Filters: map[string]any{"starred": 123},
		})
		Expect(err).ToNot(HaveOccurred())
		albums := res.(model.Albums)

		var found bool
		for _, a := range albums {
			if a.ID == albumWithoutAnnotation.ID {
				found = true
				break
			}
		}
		Expect(found).To(BeTrue(), "Item without annotation should be included when filter is ignored")
	})
})
