package child

import (
	"encoding/json"
	"strings"
)

const redactedValue = "[REDACTED]"

// Secret carries a decrypted credential into a child request builder without
// making the plaintext printable or JSON serializable. Only this package can
// reveal the value while constructing the existing child API request.
type Secret struct {
	value string
}

func NewSecret(value string) Secret {
	return Secret{value: value}
}

func (s Secret) Empty() bool {
	return s.value == ""
}

func (s Secret) String() string {
	if s.Empty() {
		return ""
	}
	return redactedValue
}

func (s Secret) GoString() string {
	return s.String()
}

func (s Secret) MarshalJSON() ([]byte, error) {
	return json.Marshal(s.String())
}

func (s Secret) reveal() string {
	return s.value
}

// DecryptedCredentials are supplied explicitly after the model layer decrypts
// the persisted ciphertext. Builders deliberately never read ciphertext from
// DataPipeline.Params as if it were a usable child credential.
type DecryptedCredentials struct {
	TiDBPassword      Secret
	S3SecretAccessKey Secret
}

type redactedRequest struct {
	Redacted bool `json:"redacted"`
}

func marshalRedactedRequest() ([]byte, error) {
	return json.Marshal(redactedRequest{Redacted: true})
}

// RedactSecrets replaces any of the given secret values in a message with a
// placeholder.
//
// It lives in this package because the plaintext must not leave it: callers
// hold a Secret, never the string inside. The intended use is scrubbing a
// downstream error before it is logged - a service that echoes its request
// back in an error would otherwise put a credential in the log.
//
// Very short values are skipped. They are not credentials, and replacing a
// one or two character string would mangle the message it appears in.
func RedactSecrets(message string, secrets ...Secret) string {
	for _, secret := range secrets {
		value := secret.reveal()
		if len(value) < 4 {
			continue
		}
		message = strings.ReplaceAll(message, value, "[REDACTED]")
	}
	return message
}
