package app

import (
	"context"
	"net/http"
	"net/url"
	"strings"

	"github.com/deluan/navidrome/assets"
	"github.com/deluan/navidrome/conf"
	"github.com/deluan/navidrome/engine/auth"
	"github.com/deluan/navidrome/model"
	"github.com/deluan/rest"
	"github.com/go-chi/chi"
	"github.com/go-chi/jwtauth"
)

type Router struct {
	ds  model.DataStore
	mux http.Handler
}

func New(ds model.DataStore) *Router {
	return &Router{ds: ds}
}

func (app *Router) Setup(path string) {
	app.mux = app.routes(path)
}

func (app *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	app.mux.ServeHTTP(w, r)
}

func (app *Router) routes(path string) http.Handler {
	r := chi.NewRouter()

	r.Post("/login", Login(app.ds))
	r.Post("/createAdmin", CreateAdmin(app.ds))

	r.Route("/api", func(r chi.Router) {
		r.Use(mapAuthHeader())
		r.Use(jwtauth.Verifier(auth.TokenAuth))
		r.Use(authenticator(app.ds))
		app.R(r, "/user", model.User{}, true)
		app.R(r, "/song", model.MediaFile{}, true)
		app.R(r, "/album", model.Album{}, true)
		app.R(r, "/artist", model.Artist{}, true)
		app.R(r, "/player", model.Player{}, true)
		app.R(r, "/playlist", model.Playlist{}, true)
		app.R(r, "/transcoding", model.Transcoding{}, conf.Server.EnableTranscodingConfig)
		app.RX(r, "/translation", newTranslationRepository, false)

		app.addPlaylistTrackRoute(r)

		// Keepalive endpoint to be used to keep the session valid (ex: while playing songs)
		r.Get("/keepalive/*", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(`{"response":"ok"}`)) })
	})

	// Serve UI app assets
	r.Handle("/", ServeIndex(app.ds, assets.AssetFile()))
	r.Handle("/*", http.StripPrefix(path, http.FileServer(assets.AssetFile())))

	return r
}

func (app *Router) R(r chi.Router, pathPrefix string, model interface{}, persistable bool) {
	constructor := func(ctx context.Context) rest.Repository {
		return app.ds.Resource(ctx, model)
	}
	app.RX(r, pathPrefix, constructor, persistable)
}

func (app *Router) RX(r chi.Router, pathPrefix string, constructor rest.RepositoryConstructor, persistable bool) {
	r.Route(pathPrefix, func(r chi.Router) {
		r.Get("/", rest.GetAll(constructor))
		if persistable {
			r.Post("/", rest.Post(constructor))
		}
		r.Route("/{id}", func(r chi.Router) {
			r.Use(UrlParams)
			r.Get("/", rest.Get(constructor))
			if persistable {
				r.Put("/", rest.Put(constructor))
				r.Delete("/", rest.Delete(constructor))
			}
		})
	})
}

type restHandler = func(rest.RepositoryConstructor, ...rest.Logger) http.HandlerFunc

func (app *Router) addPlaylistTrackRoute(r chi.Router) {
	// Add a middleware to capture the playlistId
	wrapper := func(f restHandler) http.HandlerFunc {
		return func(res http.ResponseWriter, req *http.Request) {
			c := func(ctx context.Context) rest.Repository {
				plsRepo := app.ds.Resource(ctx, model.Playlist{})
				plsId := chi.URLParam(req, "playlistId")
				return plsRepo.(model.PlaylistRepository).Tracks(plsId)
			}

			f(c).ServeHTTP(res, req)
		}
	}

	r.Route("/playlist/{playlistId}/tracks", func(r chi.Router) {
		r.Get("/", wrapper(rest.GetAll))
		r.Route("/{id}", func(r chi.Router) {
			r.Use(UrlParams)
			r.Get("/", wrapper(rest.Get))
			r.Put("/", func(w http.ResponseWriter, r *http.Request) {
				reorderItem(app.ds)(w, r)
			})
			r.Delete("/", func(w http.ResponseWriter, r *http.Request) {
				deleteFromPlaylist(app.ds)(w, r)
			})
		})
		r.With(UrlParams).Post("/", func(w http.ResponseWriter, r *http.Request) {
			addToPlaylist(app.ds)(w, r)
		})
	})
}

// Middleware to convert Chi URL params (from Context) to query params, as expected by our REST package
func UrlParams(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := chi.RouteContext(r.Context())
		parts := make([]string, 0)
		for i, key := range ctx.URLParams.Keys {
			value := ctx.URLParams.Values[i]
			if key == "*" {
				continue
			}
			parts = append(parts, url.QueryEscape(":"+key)+"="+url.QueryEscape(value))
		}
		q := strings.Join(parts, "&")
		if r.URL.RawQuery == "" {
			r.URL.RawQuery = q
		} else {
			r.URL.RawQuery += "&" + q
		}

		next.ServeHTTP(w, r)
	})
}
