package e2e

import (
	"errors"
	"net/http"
	"time"

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

// Client profile JSON bodies for getTranscodeDecision requests.
// All bitrate values are in bps (per OpenSubsonic spec).
const (
	// mp3OnlyClient can direct-play mp3 and transcode to mp3
	mp3OnlyClient = `{
		"name": "test-mp3-only",
		"directPlayProfiles": [
			{"containers": ["mp3"], "audioCodecs": ["mp3"], "protocols": ["http"]}
		],
		"transcodingProfiles": [
			{"container": "mp3", "audioCodec": "mp3", "protocol": "http"}
		]
	}`

	// flacAndMp3Client can direct-play flac and mp3, transcode to mp3
	flacAndMp3Client = `{
		"name": "test-flac-mp3",
		"directPlayProfiles": [
			{"containers": ["flac"], "audioCodecs": ["flac"], "protocols": ["http"]},
			{"containers": ["mp3"], "audioCodecs": ["mp3"], "protocols": ["http"]}
		],
		"transcodingProfiles": [
			{"container": "mp3", "audioCodec": "mp3", "protocol": "http"}
		]
	}`

	// universalClient can direct-play most formats
	universalClient = `{
		"name": "test-universal",
		"directPlayProfiles": [
			{"containers": ["mp3"], "audioCodecs": ["mp3"], "protocols": ["http"]},
			{"containers": ["flac"], "audioCodecs": ["flac"], "protocols": ["http"]},
			{"containers": ["m4a"], "audioCodecs": ["alac", "aac"], "protocols": ["http"]},
			{"containers": ["opus", "ogg"], "audioCodecs": ["opus"], "protocols": ["http"]},
			{"containers": ["wav"], "audioCodecs": ["pcm"], "protocols": ["http"]},
			{"containers": ["dsf"], "audioCodecs": ["dsd"], "protocols": ["http"]}
		],
		"transcodingProfiles": [
			{"container": "mp3", "audioCodec": "mp3", "protocol": "http"}
		]
	}`

	// bitrateCapClient has maxAudioBitrate set to 320000 bps (320 kbps)
	bitrateCapClient = `{
		"name": "test-bitrate-cap",
		"maxAudioBitrate": 320000,
		"directPlayProfiles": [
			{"containers": ["mp3"], "audioCodecs": ["mp3"], "protocols": ["http"]},
			{"containers": ["flac"], "audioCodecs": ["flac"], "protocols": ["http"]}
		],
		"transcodingProfiles": [
			{"container": "mp3", "audioCodec": "mp3", "protocol": "http"}
		]
	}`

	// opusTranscodeClient can direct-play mp3, transcode to opus
	opusTranscodeClient = `{
		"name": "test-opus-transcode",
		"directPlayProfiles": [
			{"containers": ["mp3"], "audioCodecs": ["mp3"], "protocols": ["http"]}
		],
		"transcodingProfiles": [
			{"container": "opus", "audioCodec": "opus", "protocol": "http"}
		]
	}`

	// flacOnlyClient can direct-play flac, transcode to flac (no mp3 support at all)
	flacOnlyClient = `{
		"name": "test-flac-only",
		"directPlayProfiles": [
			{"containers": ["flac"], "audioCodecs": ["flac"], "protocols": ["http"]}
		],
		"transcodingProfiles": [
			{"container": "flac", "audioCodec": "flac", "protocol": "http"}
		]
	}`

	// maxTranscodeBitrateClient has maxTranscodingAudioBitrate set
	maxTranscodeBitrateClient = `{
		"name": "test-max-transcode-bitrate",
		"maxTranscodingAudioBitrate": 192000,
		"directPlayProfiles": [
			{"containers": ["mp3"], "audioCodecs": ["mp3"], "protocols": ["http"]}
		],
		"transcodingProfiles": [
			{"container": "mp3", "audioCodec": "mp3", "protocol": "http"}
		]
	}`

	// dsdToFlacClient can direct-play mp3, transcode to flac
	dsdToFlacClient = `{
		"name": "test-dsd-to-flac",
		"directPlayProfiles": [
			{"containers": ["mp3"], "audioCodecs": ["mp3"], "protocols": ["http"]}
		],
		"transcodingProfiles": [
			{"container": "flac", "audioCodec": "flac", "protocol": "http"}
		]
	}`
)

var _ = Describe("Transcode Endpoints", Ordered, func() {
	// Track IDs resolved in BeforeAll
	var (
		mp3TrackID         string // Come Together (mp3, 320kbps)
		flacTrackID        string // TC FLAC Standard (flac, 900kbps)
		flacHiResTrackID   string // TC FLAC HiRes (flac, 3000kbps)
		flacMultichTrackID string // TC FLAC Multichannel (flac, 6ch)
		alacTrackID        string // TC ALAC Track (m4a, alac)
		dsdTrackID         string // TC DSD Track (dsf, dsd)
		opusTrackID        string // TC Opus Track (opus, 128kbps)
		mkaOpusTrackID     string // TC MKA Opus (mka, opus via codec tag)
	)

	BeforeAll(func() {
		setupTestDB()

		songs, err := ds.MediaFile(ctx).GetAll()
		Expect(err).ToNot(HaveOccurred())
		byTitle := map[string]string{}
		for _, s := range songs {
			byTitle[s.Title] = s.ID
		}
		ensureGetTrackID := func(title string) string {
			id := byTitle[title]
			Expect(id).ToNot(BeEmpty())
			return id
		}
		mp3TrackID = ensureGetTrackID("Come Together")
		flacTrackID = ensureGetTrackID("TC FLAC Standard")
		flacHiResTrackID = ensureGetTrackID("TC FLAC HiRes")
		flacMultichTrackID = ensureGetTrackID("TC FLAC Multichannel")
		alacTrackID = ensureGetTrackID("TC ALAC Track")
		dsdTrackID = ensureGetTrackID("TC DSD Track")
		opusTrackID = ensureGetTrackID("TC Opus Track")
		mkaOpusTrackID = ensureGetTrackID("TC MKA Opus")
	})

	Describe("getTranscodeDecision", func() {
		// setPlayerMaxBitRate ensures a player exists for the test-client and sets its MaxBitRate.
		// It makes a dummy request to register the player, then updates it via the repository.
		setPlayerMaxBitRate := func(maxBitRate int) {
			doReq("ping")
			player, err := ds.Player(ctx).FindMatch(adminUser.ID, "test-client", "")
			Expect(err).ToNot(HaveOccurred())
			player.MaxBitRate = maxBitRate
			Expect(ds.Player(ctx).Put(player)).To(Succeed())
		}

		AfterEach(func() {
			// Reset player MaxBitRate to 0 after each test
			player, err := ds.Player(ctx).FindMatch(adminUser.ID, "test-client", "")
			if err == nil {
				player.MaxBitRate = 0
				_ = ds.Player(ctx).Put(player)
			}
		})

		Describe("error cases", func() {
			It("returns 405 for GET request", func() {
				w := doRawReq("getTranscodeDecision", "mediaId", mp3TrackID, "mediaType", "song")
				Expect(w.Code).To(Equal(http.StatusMethodNotAllowed))
			})

			It("returns error when mediaId is missing", func() {
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusFailed))
				Expect(resp.Error).ToNot(BeNil())
				Expect(resp.Error.Code).To(Equal(responses.ErrorMissingParameter))
			})

			It("returns error when mediaType is missing", func() {
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", mp3TrackID)
				Expect(resp.Status).To(Equal(responses.StatusFailed))
				Expect(resp.Error).ToNot(BeNil())
				Expect(resp.Error.Code).To(Equal(responses.ErrorMissingParameter))
			})

			It("returns error for unsupported mediaType", func() {
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", mp3TrackID, "mediaType", "video")
				Expect(resp.Status).To(Equal(responses.StatusFailed))
				Expect(resp.Error).ToNot(BeNil())
				Expect(resp.Error.Code).To(Equal(responses.ErrorGeneric))
			})

			It("returns error for invalid JSON body", func() {
				resp := doPostReq("getTranscodeDecision", "{invalid-json", "mediaId", mp3TrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusFailed))
				Expect(resp.Error).ToNot(BeNil())
			})

			It("returns error for empty JSON body", func() {
				w := doRawPostReq("getTranscodeDecision", "", "mediaId", mp3TrackID, "mediaType", "song")
				Expect(w.Code).To(Equal(http.StatusOK)) // Subsonic errors are returned as 200 with error status
				resp := parseJSONResponse(w)
				Expect(resp.Status).To(Equal(responses.StatusFailed))
			})

			It("returns error for non-existent media ID", func() {
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", "non-existent-id", "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusFailed))
				Expect(resp.Error).ToNot(BeNil())
				Expect(resp.Error.Code).To(Equal(responses.ErrorDataNotFound))
			})

			It("returns error for invalid protocol in body", func() {
				invalidBody := `{
					"directPlayProfiles": [
						{"containers": ["mp3"], "audioCodecs": ["mp3"], "protocols": ["invalid-protocol"]}
					]
				}`
				resp := doPostReq("getTranscodeDecision", invalidBody, "mediaId", mp3TrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusFailed))
				Expect(resp.Error).ToNot(BeNil())
			})

			It("returns error for invalid comparison operator in body", func() {
				invalidBody := `{
					"directPlayProfiles": [
						{"containers": ["mp3"], "audioCodecs": ["mp3"], "protocols": ["http"]}
					],
					"codecProfiles": [{
						"type": "AudioCodec", "name": "mp3",
						"limitations": [{"name": "audioBitrate", "comparison": "InvalidOp", "values": ["320000"]}]
					}]
				}`
				resp := doPostReq("getTranscodeDecision", invalidBody, "mediaId", mp3TrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusFailed))
				Expect(resp.Error).ToNot(BeNil())
			})
		})

		Describe("direct play decisions", func() {
			It("allows MP3 direct play when client supports mp3", func() {
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", mp3TrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanDirectPlay).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).To(BeNil())
				Expect(resp.TranscodeDecision.TranscodeParams).ToNot(BeEmpty())
			})

			It("allows FLAC direct play when client supports flac", func() {
				resp := doPostReq("getTranscodeDecision", flacAndMp3Client, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanDirectPlay).To(BeTrue())
			})

			It("allows ALAC direct play via m4a container + alac codec matching", func() {
				resp := doPostReq("getTranscodeDecision", universalClient, "mediaId", alacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanDirectPlay).To(BeTrue())
			})

			It("allows Opus direct play when client supports opus", func() {
				resp := doPostReq("getTranscodeDecision", universalClient, "mediaId", opusTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanDirectPlay).To(BeTrue())
			})

			It("denies direct play when container mismatches", func() {
				// mp3OnlyClient cannot play FLAC container
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanDirectPlay).To(BeFalse())
			})

			It("denies direct play when codec mismatches", func() {
				// MKA container with opus codec — client only supports mp3
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", mkaOpusTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanDirectPlay).To(BeFalse())
			})

			It("denies direct play when maxAudioBitrate exceeded", func() {
				// bitrateCapClient caps at 320kbps, FLAC is 900kbps
				resp := doPostReq("getTranscodeDecision", bitrateCapClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanDirectPlay).To(BeFalse())
			})
		})

		Describe("transcode decisions", func() {
			It("transcodes FLAC to MP3 when client only supports MP3", func() {
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanDirectPlay).To(BeFalse())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				Expect(resp.TranscodeDecision.TranscodeStream.Container).To(Equal("mp3"))
				Expect(resp.TranscodeDecision.TranscodeStream.Codec).To(Equal("mp3"))
				Expect(resp.TranscodeDecision.TranscodeParams).ToNot(BeEmpty())
			})

			It("transcodes FLAC hi-res to Opus with correct sample rate", func() {
				resp := doPostReq("getTranscodeDecision", opusTranscodeClient, "mediaId", flacHiResTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				Expect(resp.TranscodeDecision.TranscodeStream.Codec).To(Equal("opus"))
				// Opus always outputs 48000 Hz
				Expect(resp.TranscodeDecision.TranscodeStream.AudioSamplerate).To(Equal(int32(48000)))
			})

			It("transcodes DSD to FLAC with normalized sample rate and bit depth", func() {
				resp := doPostReq("getTranscodeDecision", dsdToFlacClient, "mediaId", dsdTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				Expect(resp.TranscodeDecision.TranscodeStream.Codec).To(Equal("flac"))
				// DSD sample rate normalized: 2822400 / 8 = 352800
				Expect(resp.TranscodeDecision.TranscodeStream.AudioSamplerate).To(Equal(int32(352800)))
				// DSD 1-bit → 24-bit PCM
				Expect(resp.TranscodeDecision.TranscodeStream.AudioBitdepth).To(Equal(int32(24)))
			})

			It("refuses lossy to lossless transcoding: MP3 to FLAC", func() {
				// flacOnlyClient can't direct-play mp3, and lossy→lossless transcode is rejected
				resp := doPostReq("getTranscodeDecision", flacOnlyClient, "mediaId", mp3TrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				// MP3 is lossy, FLAC is lossless — should not allow transcoding
				Expect(resp.TranscodeDecision.CanTranscode).To(BeFalse())
				Expect(resp.TranscodeDecision.CanDirectPlay).To(BeFalse())
				Expect(resp.TranscodeDecision.TranscodeParams).To(BeEmpty())
			})

			It("caps transcode bitrate via maxTranscodingAudioBitrate", func() {
				resp := doPostReq("getTranscodeDecision", maxTranscodeBitrateClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				// maxTranscodingAudioBitrate is 192000 bps = 192 kbps → response in bps
				Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(192000)))
			})

			It("clamps multichannel FLAC to 2 channels when transcoding to MP3 (#5336)", func() {
				// mp3OnlyClient has no MaxAudioChannels set, so this exercises the
				// codec-intrinsic clamp in core/stream/codec.go (codecMaxChannels).
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", flacMultichTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.SourceStream.AudioChannels).To(Equal(int32(6)))
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				Expect(resp.TranscodeDecision.TranscodeStream.Codec).To(Equal("mp3"))
				Expect(resp.TranscodeDecision.TranscodeStream.AudioChannels).To(Equal(int32(2)))
			})
		})

		Describe("response structure", func() {
			It("has correct sourceStream details", func() {
				resp := doPostReq("getTranscodeDecision", universalClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				src := resp.TranscodeDecision.SourceStream
				Expect(src).ToNot(BeNil())
				Expect(src.Container).To(Equal("flac"))
				Expect(src.Codec).To(Equal("flac"))
				// AudioBitrate is in bps: 900 kbps * 1000 = 900000 bps
				Expect(src.AudioBitrate).To(Equal(int32(900000)))
				Expect(src.AudioSamplerate).To(Equal(int32(44100)))
				Expect(src.AudioChannels).To(Equal(int32(2)))
				Expect(src.Protocol).To(Equal("http"))
			})

			It("reports audioBitrate in bps (kbps * 1000)", func() {
				resp := doPostReq("getTranscodeDecision", universalClient, "mediaId", mp3TrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				src := resp.TranscodeDecision.SourceStream
				Expect(src).ToNot(BeNil())
				// MP3 is 320 kbps → 320000 bps
				Expect(src.AudioBitrate).To(Equal(int32(320000)))
			})
		})

		Describe("player MaxBitRate cap", func() {
			It("forces transcode when source bitrate exceeds player MaxBitRate", func() {
				setPlayerMaxBitRate(320) // 320 kbps cap

				// FLAC is 900kbps, client has no bitrate limit but player cap is 320
				resp := doPostReq("getTranscodeDecision", flacAndMp3Client, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanDirectPlay).To(BeFalse())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				Expect(resp.TranscodeDecision.TranscodeStream.Container).To(Equal("mp3"))
				// Target bitrate should be capped at player's 320kbps = 320000 bps
				Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(320000)))
			})

			It("does not affect direct play when source bitrate is under player MaxBitRate", func() {
				setPlayerMaxBitRate(500) // 500 kbps cap

				// MP3 is 320kbps, under the 500kbps player cap → direct play
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", mp3TrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanDirectPlay).To(BeTrue())
			})

			It("uses client limit when more restrictive than player MaxBitRate", func() {
				setPlayerMaxBitRate(500) // 500 kbps player cap

				// Client caps at 320kbps (bitrateCapClient), which is more restrictive than 500
				// FLAC is 900kbps → exceeds both limits → transcode
				resp := doPostReq("getTranscodeDecision", bitrateCapClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				// Client limit (320kbps) is more restrictive → 320000 bps
				Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(320000)))
			})

			It("uses player MaxBitRate when more restrictive than client limit", func() {
				setPlayerMaxBitRate(192) // 192 kbps player cap

				// Client caps at 320kbps (bitrateCapClient), player is more restrictive at 192
				// FLAC is 900kbps → transcode at 192kbps
				resp := doPostReq("getTranscodeDecision", bitrateCapClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				// Player limit (192kbps) is more restrictive → 192000 bps
				Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(192000)))
			})

			It("has no effect when player MaxBitRate is 0", func() {
				setPlayerMaxBitRate(0) // No player cap

				// FLAC with flac+mp3 client → direct play (no bitrate constraint)
				resp := doPostReq("getTranscodeDecision", flacAndMp3Client, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanDirectPlay).To(BeTrue())
			})
		})

		Describe("format-aware default bitrate", func() {
			It("uses mp3 format default (192kbps) for lossless-to-mp3 with no bitrate limits", func() {
				// mp3OnlyClient has no maxAudioBitrate or maxTranscodingAudioBitrate
				// FLAC → MP3 should use the mp3 default bitrate (192kbps), not hardcoded 256
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				Expect(resp.TranscodeDecision.TranscodeStream.Container).To(Equal("mp3"))
				// mp3 default is 192kbps = 192000 bps
				Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(192000)))
			})

			It("uses opus format default (128kbps) for lossless-to-opus with no bitrate limits", func() {
				// opusTranscodeClient has no maxAudioBitrate or maxTranscodingAudioBitrate
				// FLAC → Opus should use the opus default bitrate (128kbps)
				resp := doPostReq("getTranscodeDecision", opusTranscodeClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				Expect(resp.TranscodeDecision.TranscodeStream.Codec).To(Equal("opus"))
				// opus default is 128kbps = 128000 bps
				Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(128000)))
			})

			It("uses maxAudioBitrate as fallback for lossless-to-lossy when no maxTranscodingAudioBitrate", func() {
				// bitrateCapClient has maxAudioBitrate=320000 but no maxTranscodingAudioBitrate
				// FLAC → MP3: maxAudioBitrate (320kbps) should be used as the target
				resp := doPostReq("getTranscodeDecision", bitrateCapClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				// maxAudioBitrate is 320kbps = 320000 bps
				Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(320000)))
			})

			It("prefers maxTranscodingAudioBitrate over maxAudioBitrate for lossless-to-lossy", func() {
				// maxTranscodeBitrateClient has maxTranscodingAudioBitrate=192000
				// FLAC → MP3: should use 192kbps, not format default or maxAudioBitrate
				resp := doPostReq("getTranscodeDecision", maxTranscodeBitrateClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				// maxTranscodingAudioBitrate is 192kbps = 192000 bps
				Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(192000)))
			})
		})

		Describe("player MaxBitRate + client limits combined", func() {
			It("player MaxBitRate injects maxAudioBitrate, format default used for transcode target", func() {
				setPlayerMaxBitRate(320)

				// opusTranscodeClient has no client bitrate limits
				// Player cap injects maxAudioBitrate=320
				// FLAC (900kbps) → exceeds 320 → transcode to opus
				// Lossless→lossy: maxTranscodingAudioBitrate=0, so falls back to maxAudioBitrate=320
				resp := doPostReq("getTranscodeDecision", opusTranscodeClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				Expect(resp.TranscodeDecision.TranscodeStream.Codec).To(Equal("opus"))
				// maxAudioBitrate=320 used as fallback → 320000 bps
				Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(320000)))
			})

			It("player MaxBitRate + client maxTranscodingAudioBitrate work together", func() {
				setPlayerMaxBitRate(320)

				// maxTranscodeBitrateClient: maxTranscodingAudioBitrate=192000 (192kbps), no maxAudioBitrate
				// Player cap injects maxAudioBitrate=320
				// FLAC (900kbps) → exceeds 320 → transcode to mp3
				// Lossless→lossy: maxTranscodingAudioBitrate=192 takes priority
				resp := doPostReq("getTranscodeDecision", maxTranscodeBitrateClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
				// maxTranscodingAudioBitrate=192 is preferred → 192000 bps
				Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(192000)))
			})

			It("streams with correct bitrate after player MaxBitRate-triggered transcode", func() {
				setPlayerMaxBitRate(128)

				// Get decision: FLAC (900kbps) with player cap 128 → transcode
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				token := resp.TranscodeDecision.TranscodeParams
				Expect(token).ToNot(BeEmpty())

				// Stream using the token
				w := doRawReq("getTranscodeStream", "mediaId", flacTrackID, "mediaType", "song", "transcodeParams", token)
				Expect(w.Code).To(Equal(http.StatusOK))
				Expect(streamerSpy.LastRequest.Format).To(Equal("mp3"))
				Expect(streamerSpy.LastRequest.BitRate).To(Equal(128))
			})
		})
	})

	Describe("getTranscodeStream", func() {
		Describe("error cases", func() {
			It("returns 400 when mediaId is missing", func() {
				w := doRawReq("getTranscodeStream", "mediaType", "song", "transcodeParams", "some-token")
				Expect(w.Code).To(Equal(http.StatusBadRequest))
			})

			It("returns 400 when mediaType is missing", func() {
				w := doRawReq("getTranscodeStream", "mediaId", mp3TrackID, "transcodeParams", "some-token")
				Expect(w.Code).To(Equal(http.StatusBadRequest))
			})

			It("returns 400 when transcodeParams is missing", func() {
				w := doRawReq("getTranscodeStream", "mediaId", mp3TrackID, "mediaType", "song")
				Expect(w.Code).To(Equal(http.StatusBadRequest))
			})

			It("returns 400 for unsupported mediaType", func() {
				w := doRawReq("getTranscodeStream", "mediaId", mp3TrackID, "mediaType", "video", "transcodeParams", "some-token")
				Expect(w.Code).To(Equal(http.StatusBadRequest))
			})

			It("returns 410 for malformed token", func() {
				w := doRawReq("getTranscodeStream", "mediaId", mp3TrackID, "mediaType", "song", "transcodeParams", "invalid-token")
				Expect(w.Code).To(Equal(http.StatusGone))
			})

			It("returns 410 for stale token (media file updated after token issued)", func() {
				// Get a valid decision token
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", mp3TrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision).ToNot(BeNil())
				token := resp.TranscodeDecision.TranscodeParams
				Expect(token).ToNot(BeEmpty())

				// Save original UpdatedAt and restore after test
				mf, err := ds.MediaFile(ctx).Get(mp3TrackID)
				Expect(err).ToNot(HaveOccurred())
				originalUpdatedAt := mf.UpdatedAt

				// Update the media file's UpdatedAt to simulate a change after token issuance
				mf.UpdatedAt = time.Now().Add(time.Hour)
				Expect(ds.MediaFile(ctx).Put(mf)).To(Succeed())

				// Attempt to stream with the now-stale token
				w := doRawReq("getTranscodeStream", "mediaId", mp3TrackID, "mediaType", "song", "transcodeParams", token)
				Expect(w.Code).To(Equal(http.StatusGone))

				// Restore original UpdatedAt
				mf.UpdatedAt = originalUpdatedAt
				Expect(ds.MediaFile(ctx).Put(mf)).To(Succeed())
			})

			It("returns 500 when stream creation fails", func() {
				// Get a valid decision token
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				token := resp.TranscodeDecision.TranscodeParams
				Expect(token).ToNot(BeEmpty())

				// Simulate streamer failure (e.g., ffmpeg missing codec)
				streamerSpy.SimulateError = errors.New("ffmpeg exited with non-zero status code: 1: Unknown encoder 'libopus'")
				defer func() { streamerSpy.SimulateError = nil }()

				w := doRawReq("getTranscodeStream", "mediaId", flacTrackID, "mediaType", "song", "transcodeParams", token)
				Expect(w.Code).To(Equal(http.StatusInternalServerError))
			})

			It("returns 500 when transcoded stream is empty", func() {
				// Get a valid decision token
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				token := resp.TranscodeDecision.TranscodeParams
				Expect(token).ToNot(BeEmpty())

				// Simulate ffmpeg producing 0 bytes
				streamerSpy.SimulateEmptyStream = true
				defer func() { streamerSpy.SimulateEmptyStream = false }()

				w := doRawReq("getTranscodeStream", "mediaId", flacTrackID, "mediaType", "song", "transcodeParams", token)
				Expect(w.Code).To(Equal(http.StatusInternalServerError))
			})
		})

		Describe("round-trip: decision then stream", func() {
			It("streams direct play for MP3", func() {
				// Get decision
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", mp3TrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision.CanDirectPlay).To(BeTrue())
				token := resp.TranscodeDecision.TranscodeParams
				Expect(token).ToNot(BeEmpty())

				// Stream using the token
				w := doRawReq("getTranscodeStream", "mediaId", mp3TrackID, "mediaType", "song", "transcodeParams", token)
				Expect(w.Code).To(Equal(http.StatusOK))
				// Direct play: format should be "raw" or empty
				Expect(streamerSpy.LastRequest.Format).To(BeElementOf("raw", ""))
			})

			It("streams transcoded FLAC to MP3", func() {
				// Get decision
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", flacTrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
				token := resp.TranscodeDecision.TranscodeParams
				Expect(token).ToNot(BeEmpty())

				// Stream using the token
				w := doRawReq("getTranscodeStream", "mediaId", flacTrackID, "mediaType", "song", "transcodeParams", token)
				Expect(w.Code).To(Equal(http.StatusOK))
				Expect(streamerSpy.LastRequest.Format).To(Equal("mp3"))
			})

			It("passes offset through to stream request", func() {
				// Get decision
				resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", mp3TrackID, "mediaType", "song")
				Expect(resp.Status).To(Equal(responses.StatusOK))
				token := resp.TranscodeDecision.TranscodeParams
				Expect(token).ToNot(BeEmpty())

				// Stream with offset
				w := doRawReq("getTranscodeStream", "mediaId", mp3TrackID, "mediaType", "song",
					"transcodeParams", token, "offset", "30")
				Expect(w.Code).To(Equal(http.StatusOK))
				Expect(streamerSpy.LastRequest.Offset).To(Equal(30))
			})
		})
	})
})
