package publicurl_test

import (
	"net/http"
	"net/url"
	"testing"

	"github.com/go-chi/jwtauth/v5"
	"github.com/navidrome/navidrome/conf"
	"github.com/navidrome/navidrome/conf/configtest"
	"github.com/navidrome/navidrome/core/auth"
	"github.com/navidrome/navidrome/core/publicurl"
	"github.com/navidrome/navidrome/log"
	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/tests"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

func TestPublicURL(t *testing.T) {
	tests.Init(t, false)
	log.SetLevel(log.LevelFatal)
	RegisterFailHandler(Fail)
	RunSpecs(t, "Public URL Suite")
}

var _ = Describe("Public URL Utilities", func() {
	BeforeEach(func() {
		DeferCleanup(configtest.SetupConfig())
	})

	Describe("PublicURL", func() {
		When("ShareURL is set", func() {
			BeforeEach(func() {
				conf.Server.ShareURL = "https://share.example.com"
			})

			It("uses ShareURL as the base", func() {
				r, _ := http.NewRequest("GET", "http://localhost/test", nil)
				result := publicurl.PublicURL(r, "/path/to/resource", nil)
				Expect(result).To(Equal("https://share.example.com/path/to/resource"))
			})

			It("includes query parameters", func() {
				r, _ := http.NewRequest("GET", "http://localhost/test", nil)
				params := url.Values{"size": []string{"300"}, "format": []string{"png"}}
				result := publicurl.PublicURL(r, "/image/123", params)
				Expect(result).To(ContainSubstring("https://share.example.com/image/123"))
				Expect(result).To(ContainSubstring("size=300"))
				Expect(result).To(ContainSubstring("format=png"))
			})

			It("works without a request", func() {
				result := publicurl.PublicURL(nil, "/path/to/resource", nil)
				Expect(result).To(Equal("https://share.example.com/path/to/resource"))
			})
		})

		When("ShareURL includes a path", func() {
			BeforeEach(func() {
				conf.Server.ShareURL = "https://example.com/navi"
			})

			It("prepends the ShareURL path to the resource", func() {
				r, _ := http.NewRequest("GET", "http://localhost/test", nil)
				result := publicurl.PublicURL(r, "/share/img/hash", nil)
				Expect(result).To(Equal("https://example.com/navi/share/img/hash"))
			})

			It("prepends the ShareURL path and includes query parameters", func() {
				r, _ := http.NewRequest("GET", "http://localhost/test", nil)
				params := url.Values{"size": []string{"600"}}
				result := publicurl.PublicURL(r, "/share/img/hash", params)
				Expect(result).To(Equal("https://example.com/navi/share/img/hash?size=600"))
			})

			It("handles trailing slash in ShareURL path", func() {
				conf.Server.ShareURL = "https://example.com/navi/"
				result := publicurl.PublicURL(nil, "/share/img/hash", nil)
				Expect(result).To(Equal("https://example.com/navi/share/img/hash"))
			})
		})

		When("ShareURL is not set", func() {
			BeforeEach(func() {
				conf.Server.ShareURL = ""
			})

			It("falls back to AbsoluteURL with request", func() {
				r, _ := http.NewRequest("GET", "https://myserver.com/test", nil)
				r.Host = "myserver.com"
				result := publicurl.PublicURL(r, "/path/to/resource", nil)
				Expect(result).To(Equal("https://myserver.com/path/to/resource"))
			})

			It("falls back to localhost without request", func() {
				result := publicurl.PublicURL(nil, "/path/to/resource", nil)
				Expect(result).To(Equal("http://localhost/path/to/resource"))
			})
		})
	})

	Describe("AbsoluteURL", func() {
		When("BaseHost is set", func() {
			BeforeEach(func() {
				conf.Server.BaseHost = "configured.example.com"
				conf.Server.BaseScheme = "https"
				conf.Server.BasePath = ""
			})

			It("uses BaseHost and BaseScheme", func() {
				r, _ := http.NewRequest("GET", "http://localhost/test", nil)
				result := publicurl.AbsoluteURL(r, "/path/to/resource", nil)
				Expect(result).To(Equal("https://configured.example.com/path/to/resource"))
			})

			It("defaults to http scheme if BaseScheme is empty", func() {
				conf.Server.BaseScheme = ""
				r, _ := http.NewRequest("GET", "http://localhost/test", nil)
				result := publicurl.AbsoluteURL(r, "/path/to/resource", nil)
				Expect(result).To(Equal("http://configured.example.com/path/to/resource"))
			})
		})

		When("BaseHost is not set", func() {
			BeforeEach(func() {
				conf.Server.BaseHost = ""
				conf.Server.BasePath = ""
			})

			It("extracts host from request", func() {
				r, _ := http.NewRequest("GET", "https://request.example.com/test", nil)
				r.Host = "request.example.com"
				result := publicurl.AbsoluteURL(r, "/path/to/resource", nil)
				Expect(result).To(Equal("https://request.example.com/path/to/resource"))
			})

			It("falls back to localhost without request", func() {
				result := publicurl.AbsoluteURL(nil, "/path/to/resource", nil)
				Expect(result).To(Equal("http://localhost/path/to/resource"))
			})
		})

		When("BasePath is set", func() {
			BeforeEach(func() {
				conf.Server.BasePath = "/navidrome"
				conf.Server.BaseHost = "example.com"
				conf.Server.BaseScheme = "https"
			})

			It("prepends BasePath to the URL", func() {
				r, _ := http.NewRequest("GET", "http://localhost/test", nil)
				result := publicurl.AbsoluteURL(r, "/path/to/resource", nil)
				Expect(result).To(Equal("https://example.com/navidrome/path/to/resource"))
			})
		})

		It("passes through absolute URLs unchanged", func() {
			r, _ := http.NewRequest("GET", "http://localhost/test", nil)
			result := publicurl.AbsoluteURL(r, "https://other.example.com/path", nil)
			Expect(result).To(Equal("https://other.example.com/path"))
		})

		It("includes query parameters", func() {
			conf.Server.BaseHost = "example.com"
			conf.Server.BaseScheme = "https"
			r, _ := http.NewRequest("GET", "http://localhost/test", nil)
			params := url.Values{"key": []string{"value"}}
			result := publicurl.AbsoluteURL(r, "/path", params)
			Expect(result).To(Equal("https://example.com/path?key=value"))
		})
	})

	Describe("ImageURL", func() {
		BeforeEach(func() {
			conf.Server.ShareURL = "https://share.example.com"
			// Initialize JWT auth for token generation
			auth.TokenAuth = jwtauth.New("HS256", []byte("test secret"), nil)
		})

		It("generates a URL with the artwork token", func() {
			artID := model.NewArtworkID(model.KindAlbumArtwork, "album-123", nil)
			result := publicurl.ImageURL(nil, artID, 0)
			Expect(result).To(HavePrefix("https://share.example.com/share/img/"))
		})

		It("includes size parameter when provided", func() {
			artID := model.NewArtworkID(model.KindArtistArtwork, "artist-1", nil)
			result := publicurl.ImageURL(nil, artID, 300)
			Expect(result).To(ContainSubstring("size=300"))
		})

		It("omits size parameter when zero", func() {
			artID := model.NewArtworkID(model.KindMediaFileArtwork, "track-1", nil)
			result := publicurl.ImageURL(nil, artID, 0)
			Expect(result).ToNot(ContainSubstring("size="))
		})
	})
})
