// Copyright 2023 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package compliance

import (
	"reflect"
	"testing"

	"github.com/prometheus/alertmanager/matchers/parse"
	"github.com/prometheus/alertmanager/pkg/labels"
)

func TestCompliance(t *testing.T) {
	for _, tc := range []struct {
		input string
		want  labels.Matchers
		err   string
		skip  bool
	}{
		{
			input: `{}`,
			want:  labels.Matchers{},
			skip:  true,
		},
		{
			input: `{foo='}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "'")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: "{foo=`}",
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "`")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `{foo=\n}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "\n")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `{foo=bar\n}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar\n")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `{foo=\t}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "\\t")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `{foo=bar\t}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar\\t")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `{foo=bar\}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar\\")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `{foo=bar\\}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar\\")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `{foo=\"}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "\"")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `{foo=bar\"}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar\"")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `{foo=bar}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
				return append(ms, m)
			}(),
		},
		{
			input: `{foo="bar"}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
				return append(ms, m)
			}(),
		},
		{
			input: `{foo=~bar.*}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchRegexp, "foo", "bar.*")
				return append(ms, m)
			}(),
		},
		{
			input: `{foo=~"bar.*"}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchRegexp, "foo", "bar.*")
				return append(ms, m)
			}(),
		},
		{
			input: `{foo!=bar}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchNotEqual, "foo", "bar")
				return append(ms, m)
			}(),
		},
		{
			input: `{foo!="bar"}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchNotEqual, "foo", "bar")
				return append(ms, m)
			}(),
		},
		{
			input: `{foo!~bar.*}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchNotRegexp, "foo", "bar.*")
				return append(ms, m)
			}(),
		},
		{
			input: `{foo!~"bar.*"}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchNotRegexp, "foo", "bar.*")
				return append(ms, m)
			}(),
		},
		{
			input: `{foo="bar", baz!="quux"}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
				m2, _ := labels.NewMatcher(labels.MatchNotEqual, "baz", "quux")
				return append(ms, m, m2)
			}(),
		},
		{
			input: `{foo="bar", baz!~"quux.*"}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
				m2, _ := labels.NewMatcher(labels.MatchNotRegexp, "baz", "quux.*")
				return append(ms, m, m2)
			}(),
		},
		{
			input: `{foo="bar",baz!~".*quux", derp="wat"}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
				m2, _ := labels.NewMatcher(labels.MatchNotRegexp, "baz", ".*quux")
				m3, _ := labels.NewMatcher(labels.MatchEqual, "derp", "wat")
				return append(ms, m, m2, m3)
			}(),
		},
		{
			input: `{foo="bar", baz!="quux", derp="wat"}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
				m2, _ := labels.NewMatcher(labels.MatchNotEqual, "baz", "quux")
				m3, _ := labels.NewMatcher(labels.MatchEqual, "derp", "wat")
				return append(ms, m, m2, m3)
			}(),
		},
		{
			input: `{foo="bar", baz!~".*quux.*", derp="wat"}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
				m2, _ := labels.NewMatcher(labels.MatchNotRegexp, "baz", ".*quux.*")
				m3, _ := labels.NewMatcher(labels.MatchEqual, "derp", "wat")
				return append(ms, m, m2, m3)
			}(),
		},
		{
			input: `{foo="bar", instance=~"some-api.*"}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
				m2, _ := labels.NewMatcher(labels.MatchRegexp, "instance", "some-api.*")
				return append(ms, m, m2)
			}(),
		},
		{
			input: `{foo=""}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "")
				return append(ms, m)
			}(),
		},
		{
			input: `{foo="bar,quux", job="job1"}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar,quux")
				m2, _ := labels.NewMatcher(labels.MatchEqual, "job", "job1")
				return append(ms, m, m2)
			}(),
		},
		{
			input: `{foo = "bar", dings != "bums", }`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
				m2, _ := labels.NewMatcher(labels.MatchNotEqual, "dings", "bums")
				return append(ms, m, m2)
			}(),
		},
		{
			input: `foo=bar,dings!=bums`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
				m2, _ := labels.NewMatcher(labels.MatchNotEqual, "dings", "bums")
				return append(ms, m, m2)
			}(),
		},
		{
			input: `{quote="She said: \"Hi, ladies! That's gender-neutral…\""}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "quote", `She said: "Hi, ladies! That's gender-neutral…"`)
				return append(ms, m)
			}(),
		},
		{
			input: `statuscode=~"5.."`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchRegexp, "statuscode", "5..")
				return append(ms, m)
			}(),
		},
		{
			input: `tricky=~~~`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchRegexp, "tricky", "~~")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `trickier==\\=\=\"`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "trickier", `=\=\="`)
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `contains_quote != "\"" , contains_comma !~ "foo,bar" , `,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchNotEqual, "contains_quote", `"`)
				m2, _ := labels.NewMatcher(labels.MatchNotRegexp, "contains_comma", "foo,bar")
				return append(ms, m, m2)
			}(),
		},
		{
			input: `{foo=bar}}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar}")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `{foo=bar}},}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar}}")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `{foo=,bar=}}`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m1, _ := labels.NewMatcher(labels.MatchEqual, "foo", "")
				m2, _ := labels.NewMatcher(labels.MatchEqual, "bar", "}")
				return append(ms, m1, m2)
			}(),
			skip: true,
		},
		{
			input: `job=`,
			want: func() labels.Matchers {
				m, _ := labels.NewMatcher(labels.MatchEqual, "job", "")
				return labels.Matchers{m}
			}(),
			skip: true,
		},
		{
			input: `{name-with-dashes = "bar"}`,
			want: func() labels.Matchers {
				m, _ := labels.NewMatcher(labels.MatchEqual, "name-with-dashes", "bar")
				return labels.Matchers{m}
			}(),
		},
		{
			input: `{,}`,
			err:   "bad matcher format: ",
		},
		{
			input: `job="value`,
			err:   `matcher value contains unescaped double quote: "value`,
		},
		{
			input: `job=value"`,
			err:   `matcher value contains unescaped double quote: value"`,
		},
		{
			input: `trickier==\\=\=\""`,
			err:   `matcher value contains unescaped double quote: =\\=\=\""`,
		},
		{
			input: `contains_unescaped_quote = foo"bar`,
			err:   `matcher value contains unescaped double quote: foo"bar`,
		},
		{
			input: `{foo=~"invalid[regexp"}`,
			err:   "error parsing regexp: missing closing ]: `[regexp)$`",
		},
		// Double escaped strings.
		{
			input: `"{foo=\"bar"}`,
			err:   `bad matcher format: "{foo=\"bar"`,
		},
		{
			input: `"foo=\"bar"`,
			err:   `bad matcher format: "foo=\"bar"`,
		},
		{
			input: `"foo=\"bar\""`,
			err:   `bad matcher format: "foo=\"bar\""`,
		},
		{
			input: `"foo=\"bar\"`,
			err:   `bad matcher format: "foo=\"bar\"`,
		},
		{
			input: `"{foo=\"bar\"}"`,
			err:   `bad matcher format: "{foo=\"bar\"}"`,
		},
		{
			input: `"foo="bar""`,
			err:   `bad matcher format: "foo="bar""`,
		},
		{
			input: `{{foo=`,
			err:   `bad matcher format: {foo=`,
		},
		{
			input: `{foo=`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "")
				return append(ms, m)
			}(),
			skip: true,
		},
		{
			input: `{foo=}b`,
			want: func() labels.Matchers {
				ms := labels.Matchers{}
				m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "}b")
				return append(ms, m)
			}(),
			skip: true,
		},
	} {
		t.Run(tc.input, func(t *testing.T) {
			if tc.skip {
				t.Skip()
			}
			got, err := parse.Matchers(tc.input)
			if err != nil && tc.err == "" {
				t.Fatalf("got error where none expected: %v", err)
			}
			if err == nil && tc.err != "" {
				t.Fatalf("expected error but got none: %v", tc.err)
			}
			if !reflect.DeepEqual(got, tc.want) {
				t.Fatalf("matchers not equal:\ngot %s\nwant %s", got, tc.want)
			}
		})
	}
}
