package main

import (
	"net/http"
	"net/url"
	"path"
	"slices"
	"strings"
)

func mergeURLs(uiURL, requestURI *url.URL, dropSrcPathPrefixParts int, mergeQueryArgs []string) *url.URL {
	targetURL := *uiURL

	srcPath := dropPrefixParts(requestURI.Path, dropSrcPathPrefixParts)
	if strings.HasPrefix(srcPath, "/") {
		targetURL.Path = strings.TrimSuffix(targetURL.Path, "/")
	}
	targetURL.Path += srcPath
	requestParams := requestURI.Query()

	if len(requestParams) == 0 {
		return &targetURL
	}

	// Merge client query args with backend query args
	targetParams := targetURL.Query()
	uiParams := url.Values{}

	// Copy all the target query args
	for k, v := range targetParams {
		for i := range v {
			uiParams.Add(k, v[i])
		}
	}

	// Copy the client query args if they do not clash with target args.
	for k, v := range requestParams {
		if targetParams.Has(k) && !slices.Contains(mergeQueryArgs, k) {
			// Skip clashed client query params for security reasons
			continue
		}
		for i := range v {
			uiParams.Add(k, v[i])
		}
	}

	targetURL.RawQuery = uiParams.Encode()
	return &targetURL
}

func dropPrefixParts(path string, parts int) string {
	if parts <= 0 {
		return path
	}
	for parts > 0 {
		path = strings.TrimPrefix(path, "/")
		n := strings.IndexByte(path, '/')
		if n < 0 {
			return ""
		}
		path = path[n:]
		parts--
	}
	return path
}

func (ui *UserInfo) getURLPrefixAndHeaders(u *url.URL, host string, h http.Header) (*URLPrefix, HeadersConf) {
	for _, e := range ui.URLMaps {
		if !matchAnyRegex(e.SrcHosts, host) {
			continue
		}
		if !matchAnyRegex(e.SrcPaths, u.Path) {
			continue
		}
		if !matchAnyQueryArg(e.SrcQueryArgs, u.Query()) {
			continue
		}
		if !matchAnyHeader(e.SrcHeaders, h) {
			continue
		}

		return e.URLPrefix, e.HeadersConf
	}
	if ui.URLPrefix != nil {
		return ui.URLPrefix, ui.HeadersConf
	}
	return nil, HeadersConf{}
}

func matchAnyRegex(rs []*Regex, s string) bool {
	if len(rs) == 0 {
		return true
	}
	for _, r := range rs {
		if r.match(s) {
			return true
		}
	}
	return false
}

func matchAnyQueryArg(qas []*QueryArg, args url.Values) bool {
	if len(qas) == 0 {
		return true
	}
	for _, qa := range qas {
		vs, ok := args[qa.Name]
		if !ok {
			continue
		}
		for _, v := range vs {
			if qa.Value.match(v) {
				return true
			}
		}
	}
	return false
}

func matchAnyHeader(headers []*Header, h http.Header) bool {
	if len(headers) == 0 {
		return true
	}
	for _, header := range headers {
		if slices.Contains(h.Values(header.Name), header.Value) {
			return true
		}
	}
	return false
}

func normalizeURL(uOrig *url.URL) *url.URL {
	u := *uOrig
	// Prevent from attacks with using `..` in r.URL.Path
	u.Path = path.Clean(u.Path)
	if u.Path == "." {
		u.Path = "/"
	}
	if !strings.HasSuffix(u.Path, "/") && strings.HasSuffix(uOrig.Path, "/") {
		// The path.Clean() removes trailing slash.
		// Return it back if needed.
		// This should fix https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1752
		u.Path += "/"
	}
	if !strings.HasPrefix(u.Path, "/") {
		u.Path = "/" + u.Path
	}
	if u.Path == "/" {
		// See https://github.com/VictoriaMetrics/VictoriaMetrics/pull/1554
		u.Path = ""
	}
	return &u
}
