package mock_prototool

import (
	"bytes"
	"net/textproto"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/prototool"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/matcher"
	"go.uber.org/mock/gomock"
	"google.golang.org/protobuf/testing/protocmp"
)

func Equate() cmp.Option {
	return cmp.Options{
		protocmp.FilterField(&prototool.HeaderKV{}, "key", cmp.Comparer(func(x, y []byte) bool {
			// Compare normalized forms.
			return textproto.CanonicalMIMEHeaderKey(string(x)) == textproto.CanonicalMIMEHeaderKey(string(y))
		})),
		protocmp.SortRepeated(sortHeaderKV), // sort when *prototool.HeaderKV is part of a proto message
		cmpopts.SortSlices(sortHeaderKV),    // sort when *prototool.HeaderKV is not part of a proto message
		protocmp.SortRepeated(sortQueryKV),  // sort when *prototool.QueryKV is part of a proto message
		cmpopts.SortSlices(sortQueryKV),     // sort when *prototool.QueryKV is not part of a proto message
	}
}

// ProtoEq is like matcher.ProtoEq(..., ..., Equate()).
func ProtoEq(t *testing.T, msg any, opts ...cmp.Option) gomock.Matcher {
	return matcher.ProtoEq(t, msg, append(opts, Equate())...)
}

func sortHeaderKV(x, y *prototool.HeaderKV) bool {
	if x == y { // both nil or equal pointers
		return false // not less
	}
	if x == nil && y != nil {
		return true // nil < non-nil
	}
	if x != nil && y == nil {
		return false // not less
	}
	// Sort as if keys are in their normalized form.
	return textproto.CanonicalMIMEHeaderKey(string(x.Key)) < textproto.CanonicalMIMEHeaderKey(string(y.Key))
}

func NewHeaderKV(key string, values ...string) *prototool.HeaderKV {
	v := make([][]byte, 0, len(values))
	for _, val := range values {
		v = append(v, []byte(val))
	}
	return &prototool.HeaderKV{
		Key: []byte(key),
		Value: &prototool.HeaderValues{
			Value: v,
		},
	}
}

func sortQueryKV(x, y *prototool.QueryKV) bool {
	if x == y { // both nil or equal pointers
		return false // not less
	}
	if x == nil && y != nil {
		return true // nil < non-nil
	}
	if x != nil && y == nil {
		return false // not less
	}
	return bytes.Compare(x.Key, y.Key) == -1
}

func NewQueryKV(key string, values ...string) *prototool.QueryKV {
	v := make([][]byte, 0, len(values))
	for _, val := range values {
		v = append(v, []byte(val))
	}
	return &prototool.QueryKV{
		Key: []byte(key),
		Value: &prototool.QueryValues{
			Value: v,
		},
	}
}

func ValidHeaderName() string {
	// Does not start with :, which is valid as the first character.
	return "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'*+-.^_|~`"
}

// ValidHeaderValue validates the name of a header
// ^\\x00-\\x08\\x0A-\\x1F\\x7F
func ValidHeaderValue() string {
	var name []byte
	name = append(name, 'c', 0x09) // c to avoid starting with a non-printable char
	for b := 0x20; b <= 0x7E; b++ {
		name = append(name, byte(b))
	}
	for b := 0x80; b <= 0xFF; b++ {
		name = append(name, byte(b))
	}
	return string(name)
}
