package apimiddleware

import (
	"encoding/base64"
	"net/http"
	"net/url"
	"strings"

	"github.com/gorilla/mux"
	butil "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
	"github.com/wealdtech/go-bytesutil"
)

// HandleURLParameters processes URL parameters, allowing parameterized URLs to be safely and correctly proxied to grpc-gateway.
func HandleURLParameters(url string, req *http.Request, literals []string) ErrorJson {
	segments := strings.Split(url, "/")

segmentsLoop:
	for i, s := range segments {
		// We only care about segments which are parameterized.
		if isRequestParam(s) {
			// Don't do anything with parameters which should be forwarded literally to gRPC.
			for _, l := range literals {
				if s == "{"+l+"}" {
					continue segmentsLoop
				}
			}

			routeVar := mux.Vars(req)[s[1:len(s)-1]]
			bRouteVar := []byte(routeVar)
			if butil.IsHex(bRouteVar) {
				var err error
				bRouteVar, err = bytesutil.FromHexString(string(bRouteVar))
				if err != nil {
					return InternalServerErrorWithMessage(err, "could not process URL parameter")
				}
			}
			// Converting hex to base64 may result in a value which malforms the URL.
			// We use URLEncoding to safely escape such values.
			base64RouteVar := base64.URLEncoding.EncodeToString(bRouteVar)

			// Merge segments back into the full URL.
			splitPath := strings.Split(req.URL.Path, "/")
			splitPath[i] = base64RouteVar
			req.URL.Path = strings.Join(splitPath, "/")
		}
	}
	return nil
}

// HandleQueryParameters processes query parameters, allowing them to be safely and correctly proxied to grpc-gateway.
func HandleQueryParameters(req *http.Request, params []QueryParam) ErrorJson {
	queryParams := req.URL.Query()

	normalizeQueryValues(queryParams)

	for key, vals := range queryParams {
		for _, p := range params {
			if key == p.Name {
				if p.Hex {
					queryParams.Del(key)
					for _, v := range vals {
						b := []byte(v)
						if butil.IsHex(b) {
							var err error
							b, err = bytesutil.FromHexString(v)
							if err != nil {
								return InternalServerErrorWithMessage(err, "could not process query parameter")
							}
						}
						queryParams.Add(key, base64.URLEncoding.EncodeToString(b))
					}
				}
				if p.Enum {
					queryParams.Del(key)
					for _, v := range vals {
						// gRPC expects uppercase enum values.
						queryParams.Add(key, strings.ToUpper(v))
					}
				}
			}
		}
	}
	req.URL.RawQuery = queryParams.Encode()
	return nil
}

// isRequestParam verifies whether the passed string is a request parameter.
// Request parameters are enclosed in { and }.
func isRequestParam(s string) bool {
	return len(s) > 2 && s[0] == '{' && s[len(s)-1] == '}'
}

func normalizeQueryValues(queryParams url.Values) {
	// Replace comma-separated values with individual values.
	for key, vals := range queryParams {
		splitVals := make([]string, 0)
		for _, v := range vals {
			splitVals = append(splitVals, strings.Split(v, ",")...)
		}
		queryParams[key] = splitVals
	}
}
