package e2e

import (
	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/server/subsonic/responses"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("Media Annotation Endpoints", Ordered, func() {
	BeforeAll(func() {
		setupTestDB()
	})

	Describe("Star/Unstar", Ordered, func() {
		var songID, albumID, artistID string

		BeforeAll(func() {
			// Look up a song from the scanned data
			songs, err := ds.MediaFile(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "title"})
			Expect(err).ToNot(HaveOccurred())
			Expect(songs).ToNot(BeEmpty())
			songID = songs[0].ID

			// Look up an album
			albums, err := ds.Album(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "name"})
			Expect(err).ToNot(HaveOccurred())
			Expect(albums).ToNot(BeEmpty())
			albumID = albums[0].ID

			// Look up an artist
			artists, err := ds.Artist(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "name"})
			Expect(err).ToNot(HaveOccurred())
			Expect(artists).ToNot(BeEmpty())
			artistID = artists[0].ID
		})

		It("stars a song by id", func() {
			resp := doReq("star", "id", songID)

			Expect(resp.Status).To(Equal(responses.StatusOK))
		})

		It("starred song appears in getStarred response", func() {
			resp := doReq("getStarred")

			Expect(resp.Status).To(Equal(responses.StatusOK))
			Expect(resp.Starred).ToNot(BeNil())
			Expect(resp.Starred.Song).To(HaveLen(1))
			Expect(resp.Starred.Song[0].Id).To(Equal(songID))
		})

		It("unstars a previously starred song", func() {
			resp := doReq("unstar", "id", songID)

			Expect(resp.Status).To(Equal(responses.StatusOK))

			// Verify song no longer appears in starred
			resp = doReq("getStarred")

			Expect(resp.Starred.Song).To(BeEmpty())
		})

		It("stars an album by albumId", func() {
			resp := doReq("star", "albumId", albumID)

			Expect(resp.Status).To(Equal(responses.StatusOK))

			// Verify album appears in starred
			resp = doReq("getStarred")

			Expect(resp.Starred.Album).To(HaveLen(1))
			Expect(resp.Starred.Album[0].Id).To(Equal(albumID))
		})

		It("stars an artist by artistId", func() {
			resp := doReq("star", "artistId", artistID)

			Expect(resp.Status).To(Equal(responses.StatusOK))

			// Verify artist appears in starred
			resp = doReq("getStarred")

			Expect(resp.Starred.Artist).To(HaveLen(1))
			Expect(resp.Starred.Artist[0].Id).To(Equal(artistID))
		})

		It("returns error when no id provided", func() {
			resp := doReq("star")

			Expect(resp.Status).To(Equal(responses.StatusFailed))
			Expect(resp.Error).ToNot(BeNil())
		})
	})

	Describe("SetRating", Ordered, func() {
		var songID, albumID string

		BeforeAll(func() {
			songs, err := ds.MediaFile(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "title"})
			Expect(err).ToNot(HaveOccurred())
			Expect(songs).ToNot(BeEmpty())
			songID = songs[0].ID

			albums, err := ds.Album(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "name"})
			Expect(err).ToNot(HaveOccurred())
			Expect(albums).ToNot(BeEmpty())
			albumID = albums[0].ID
		})

		It("sets rating on a song", func() {
			resp := doReq("setRating", "id", songID, "rating", "4")

			Expect(resp.Status).To(Equal(responses.StatusOK))
		})

		It("rated song has correct userRating in getSong", func() {
			resp := doReq("getSong", "id", songID)

			Expect(resp.Status).To(Equal(responses.StatusOK))
			Expect(resp.Song).ToNot(BeNil())
			Expect(resp.Song.UserRating).To(Equal(int32(4)))
		})

		It("sets rating on an album", func() {
			resp := doReq("setRating", "id", albumID, "rating", "3")

			Expect(resp.Status).To(Equal(responses.StatusOK))
		})

		It("returns error for missing parameters", func() {
			// Missing both id and rating
			resp := doReq("setRating")
			Expect(resp.Status).To(Equal(responses.StatusFailed))

			// Missing rating
			resp = doReq("setRating", "id", songID)
			Expect(resp.Status).To(Equal(responses.StatusFailed))
		})
	})

	Describe("Scrobble", func() {
		It("submits a scrobble for a song", func() {
			songs, err := ds.MediaFile(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "title"})
			Expect(err).ToNot(HaveOccurred())
			Expect(songs).ToNot(BeEmpty())

			resp := doReq("scrobble", "id", songs[0].ID, "submission", "true")

			Expect(resp.Status).To(Equal(responses.StatusOK))
		})

		It("returns error when id is missing", func() {
			resp := doReq("scrobble")

			Expect(resp.Status).To(Equal(responses.StatusFailed))
			Expect(resp.Error).ToNot(BeNil())
			Expect(resp.Error.Code).To(Equal(responses.ErrorMissingParameter))
		})
	})

	Describe("ReportPlayback", Ordered, func() {
		var songID string

		BeforeAll(func() {
			songs, err := ds.MediaFile(ctx).GetAll(model.QueryOptions{Max: 1, Sort: "title"})
			Expect(err).ToNot(HaveOccurred())
			Expect(songs).ToNot(BeEmpty())
			songID = songs[0].ID
		})

		It("returns error when required params are missing", func() {
			resp := doReq("reportPlayback")
			Expect(resp.Status).To(Equal(responses.StatusFailed))
		})

		It("returns error for invalid state", func() {
			resp := doReq("reportPlayback",
				"mediaId", songID,
				"mediaType", "song",
				"positionMs", "0",
				"state", "invalid",
			)
			Expect(resp.Status).To(Equal(responses.StatusFailed))
		})

		It("starting report creates a getNowPlaying entry", func() {
			resp := doReq("reportPlayback",
				"mediaId", songID,
				"mediaType", "song",
				"positionMs", "0",
				"state", "starting",
			)
			Expect(resp.Status).To(Equal(responses.StatusOK))

			np := doReq("getNowPlaying")
			Expect(np.Status).To(Equal(responses.StatusOK))
			Expect(np.NowPlaying.Entry).To(HaveLen(1))
			Expect(np.NowPlaying.Entry[0].Id).To(Equal(songID))
			Expect(np.NowPlaying.Entry[0].State).To(Equal("starting"))
		})

		It("playing report updates getNowPlaying state and position", func() {
			resp := doReq("reportPlayback",
				"mediaId", songID,
				"mediaType", "song",
				"positionMs", "30000",
				"state", "playing",
			)
			Expect(resp.Status).To(Equal(responses.StatusOK))

			np := doReq("getNowPlaying")
			Expect(np.NowPlaying.Entry).To(HaveLen(1))
			Expect(np.NowPlaying.Entry[0].State).To(Equal("playing"))
			Expect(np.NowPlaying.Entry[0].PositionMs).To(BeNumerically(">=", int64(30000)))
		})

		It("paused report freezes position in getNowPlaying", func() {
			resp := doReq("reportPlayback",
				"mediaId", songID,
				"mediaType", "song",
				"positionMs", "30000",
				"state", "paused",
			)
			Expect(resp.Status).To(Equal(responses.StatusOK))

			np := doReq("getNowPlaying")
			Expect(np.NowPlaying.Entry).To(HaveLen(1))
			Expect(np.NowPlaying.Entry[0].State).To(Equal("paused"))
			Expect(np.NowPlaying.Entry[0].PositionMs).To(Equal(int64(30000)))
		})

		It("stopped report removes entry from getNowPlaying", func() {
			resp := doReq("reportPlayback",
				"mediaId", songID,
				"mediaType", "song",
				"positionMs", "90000",
				"state", "stopped",
			)
			Expect(resp.Status).To(Equal(responses.StatusOK))

			np := doReq("getNowPlaying")
			Expect(np.NowPlaying.Entry).To(BeEmpty())
		})

		It("accepts mediaType=podcast without error", func() {
			resp := doReq("reportPlayback",
				"mediaId", songID,
				"mediaType", "podcast",
				"positionMs", "0",
				"state", "starting",
			)
			Expect(resp.Status).To(Equal(responses.StatusOK))
		})

		It("accepts optional playbackRate and ignoreScrobble", func() {
			resp := doReq("reportPlayback",
				"mediaId", songID,
				"mediaType", "song",
				"positionMs", "5000",
				"state", "playing",
				"playbackRate", "1.5",
				"ignoreScrobble", "true",
			)
			Expect(resp.Status).To(Equal(responses.StatusOK))

			np := doReq("getNowPlaying")
			Expect(np.NowPlaying.Entry).To(HaveLen(1))
			Expect(np.NowPlaying.Entry[0].PlaybackRate).To(Equal(1.5))
		})
	})
})
