package restic

import (
	"net/url"
	"strings"
)

// mergeRestCreds embeds basic-auth user:pass into a `rest:` URL, only
// at the moment we hand it off to the restic subprocess. The result
// is intentionally NOT stored on Env or logged — restic's REST
// backend reads basic-auth from the URL only, so we have nowhere
// else to put them. Callers must treat the return value as
// secret-bearing and feed it straight into exec env.
//
// No-ops when:
//   - the URL has no `rest:` prefix (other backends — s3, b2, sftp,
//     etc. — get creds via their own env vars);
//   - the URL already embeds user:pass (operator typed creds inline);
//   - username is empty.
//
// Returns rawURL unchanged if it can't be parsed; restic will then
// reject it and the operator gets a clear error rather than a silent
// "I quietly stripped your URL" surprise.
func mergeRestCreds(rawURL, username, password string) string {
	if !strings.HasPrefix(rawURL, "rest:") {
		return rawURL
	}
	if username == "" {
		return rawURL
	}
	inner := strings.TrimPrefix(rawURL, "rest:")
	u, err := url.Parse(inner)
	if err != nil || u.Host == "" {
		// Either unparseable or a relative URL we shouldn't touch —
		// pass through and let restic complain with a clear message.
		return rawURL
	}
	if u.User != nil {
		// Operator already embedded creds — don't overwrite.
		return rawURL
	}
	u.User = url.UserPassword(username, password)
	return "rest:" + u.String()
}

// RedactURL returns a logging-safe version of u with any password in
// the userinfo replaced by ***. Mirrors restic's own redaction so
// our logs match what restic prints. Use this — never the bare URL —
// whenever a URL might end up in slog output, audit entries, or any
// surface an operator can read.
//
// Non-restic URLs (s3, b2, sftp, …) pass through unchanged unless
// they happen to embed userinfo, in which case we redact the same
// way for consistency.
func RedactURL(u string) string {
	prefix := ""
	rest := u
	if i := strings.Index(u, ":"); i > 0 && i+3 < len(u) && u[i+1:i+3] == "//" {
		// scheme://… — keep "scheme:" intact.
		prefix = u[:i+1]
		rest = u[i+1:]
	} else if strings.HasPrefix(u, "rest:") {
		prefix = "rest:"
		rest = strings.TrimPrefix(u, "rest:")
	}
	parsed, err := url.Parse(rest)
	if err != nil || parsed.User == nil {
		return u
	}
	if _, hasPass := parsed.User.Password(); !hasPass {
		return u
	}
	// Build the redacted form by hand rather than via url.URL.String(),
	// which percent-encodes the redaction marker into "%2A%2A%2A".
	user := parsed.User.Username()
	parsed.User = nil
	rebuilt := parsed.String()
	// rebuilt is "scheme://host/path…"; splice user:***@ in after "//".
	const sep = "//"
	idx := strings.Index(rebuilt, sep)
	if idx < 0 {
		return u
	}
	return prefix + rebuilt[:idx+len(sep)] + user + ":***@" + rebuilt[idx+len(sep):]
}
