package subsonic

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

	"github.com/deluan/navidrome/engine"
	"github.com/deluan/navidrome/log"
	"github.com/deluan/navidrome/model"
	"github.com/deluan/navidrome/model/request"
	"github.com/deluan/navidrome/server/subsonic/responses"
	"github.com/deluan/navidrome/utils"
)

const (
	cookieExpiry = 365 * 24 * 3600 // One year
)

func postFormToQueryParams(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		err := r.ParseForm()
		if err != nil {
			SendError(w, r, NewError(responses.ErrorGeneric, err.Error()))
		}
		var parts []string
		for key, values := range r.Form {
			for _, v := range values {
				parts = append(parts, url.QueryEscape(key)+"="+url.QueryEscape(v))
			}
		}
		r.URL.RawQuery = strings.Join(parts, "&")

		next.ServeHTTP(w, r)
	})
}

func checkRequiredParameters(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requiredParameters := []string{"u", "v", "c"}

		for _, p := range requiredParameters {
			if utils.ParamString(r, p) == "" {
				msg := fmt.Sprintf(`Missing required parameter "%s"`, p)
				log.Warn(r, msg)
				SendError(w, r, NewError(responses.ErrorMissingParameter, msg))
				return
			}
		}

		username := utils.ParamString(r, "u")
		client := utils.ParamString(r, "c")
		version := utils.ParamString(r, "v")
		ctx := r.Context()
		ctx = request.WithUsername(ctx, username)
		ctx = request.WithClient(ctx, client)
		ctx = request.WithVersion(ctx, version)
		log.Debug(ctx, "API: New request "+r.URL.Path, "username", username, "client", client, "version", version)

		r = r.WithContext(ctx)
		next.ServeHTTP(w, r)
	})
}

func authenticate(users engine.Users) func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			username := utils.ParamString(r, "u")
			pass := utils.ParamString(r, "p")
			token := utils.ParamString(r, "t")
			salt := utils.ParamString(r, "s")
			jwt := utils.ParamString(r, "jwt")

			usr, err := users.Authenticate(r.Context(), username, pass, token, salt, jwt)
			if err == model.ErrInvalidAuth {
				log.Warn(r, "Invalid login", "username", username, err)
			} else if err != nil {
				log.Error(r, "Error authenticating username", "username", username, err)
			}

			if err != nil {
				log.Warn(r, "Invalid login", "username", username)
				SendError(w, r, NewError(responses.ErrorAuthenticationFail))
				return
			}

			ctx := r.Context()
			ctx = request.WithUser(ctx, *usr)
			r = r.WithContext(ctx)

			next.ServeHTTP(w, r)
		})
	}
}

func getPlayer(players engine.Players) func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			ctx := r.Context()
			userName, _ := request.UsernameFrom(ctx)
			client, _ := request.ClientFrom(ctx)
			playerId := playerIDFromCookie(r, userName)
			ip, _, _ := net.SplitHostPort(r.RemoteAddr)
			player, trc, err := players.Register(ctx, playerId, client, r.Header.Get("user-agent"), ip)
			if err != nil {
				log.Error("Could not register player", "username", userName, "client", client)
			} else {
				ctx = request.WithPlayer(ctx, *player)
				if trc != nil {
					ctx = request.WithTranscoding(ctx, *trc)
				}
				r = r.WithContext(ctx)

				cookie := &http.Cookie{
					Name:     playerIDCookieName(userName),
					Value:    player.ID,
					MaxAge:   cookieExpiry,
					HttpOnly: true,
					Path:     "/",
				}
				http.SetCookie(w, cookie)
			}

			next.ServeHTTP(w, r)
		})
	}
}

func playerIDFromCookie(r *http.Request, userName string) string {
	cookieName := playerIDCookieName(userName)
	var playerId string
	if c, err := r.Cookie(cookieName); err == nil {
		playerId = c.Value
		log.Trace(r, "playerId found in cookies", "playerId", playerId)
	}
	return playerId
}

func playerIDCookieName(userName string) string {
	cookieName := fmt.Sprintf("nd-player-%x", userName)
	return cookieName
}
