// Copyright 2018 Prometheus Team
// 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 config

import (
	"errors"
	"net/mail"
	"reflect"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
	"gopkg.in/yaml.v2"
)

func TestEmailToIsPresent(t *testing.T) {
	in := `
to: ''
`
	var cfg EmailConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)

	expected := "missing to address in email config"

	if err == nil {
		t.Fatalf("no error returned, expected:\n%v", expected)
	}
	if err.Error() != expected {
		t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
	}
}

func TestEmailHeadersCollision(t *testing.T) {
	in := `
to: 'to@email.com'
headers:
  Subject: 'Alert'
  sUbject: 'New Alert'
`
	var cfg EmailConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)

	expected := "duplicate header \"Subject\" in email config"

	if err == nil {
		t.Fatalf("no error returned, expected:\n%v", expected)
	}
	if err.Error() != expected {
		t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
	}
}

func TestEmailToAllowsMultipleAdresses(t *testing.T) {
	in := `
to: 'a@example.com, ,b@example.com,c@example.com'
`
	var cfg EmailConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)
	if err != nil {
		t.Fatal(err)
	}

	expected := []*mail.Address{
		{Address: "a@example.com"},
		{Address: "b@example.com"},
		{Address: "c@example.com"},
	}

	res, err := mail.ParseAddressList(cfg.To)
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(res, expected) {
		t.Fatalf("expected %v, got %v", expected, res)
	}
}

func TestEmailDisallowMalformed(t *testing.T) {
	in := `
to: 'a@'
`
	var cfg EmailConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)
	if err != nil {
		t.Fatal(err)
	}
	_, err = mail.ParseAddressList(cfg.To)
	if err == nil {
		t.Fatalf("no error returned, expected:\n%v", "mail: no angle-addr")
	}
}

func TestPagerdutyTestRoutingKey(t *testing.T) {
	t.Run("error if no routing key or key file", func(t *testing.T) {
		in := `
routing_key: ''
`
		var cfg PagerdutyConfig
		err := yaml.UnmarshalStrict([]byte(in), &cfg)

		expected := "missing service or routing key in PagerDuty config"

		if err == nil {
			t.Fatalf("no error returned, expected:\n%v", expected)
		}
		if err.Error() != expected {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
		}
	})

	t.Run("error if both routing key and key file", func(t *testing.T) {
		in := `
routing_key: 'xyz'
routing_key_file: 'xyz'
`
		var cfg PagerdutyConfig
		err := yaml.UnmarshalStrict([]byte(in), &cfg)

		expected := "at most one of routing_key & routing_key_file must be configured"

		if err == nil {
			t.Fatalf("no error returned, expected:\n%v", expected)
		}
		if err.Error() != expected {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
		}
	})
}

func TestPagerdutyServiceKey(t *testing.T) {
	t.Run("error if no service key or key file", func(t *testing.T) {
		in := `
service_key: ''
`
		var cfg PagerdutyConfig
		err := yaml.UnmarshalStrict([]byte(in), &cfg)

		expected := "missing service or routing key in PagerDuty config"

		if err == nil {
			t.Fatalf("no error returned, expected:\n%v", expected)
		}
		if err.Error() != expected {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
		}
	})

	t.Run("error if both service key and key file", func(t *testing.T) {
		in := `
service_key: 'xyz'
service_key_file: 'xyz'
`
		var cfg PagerdutyConfig
		err := yaml.UnmarshalStrict([]byte(in), &cfg)

		expected := "at most one of service_key & service_key_file must be configured"

		if err == nil {
			t.Fatalf("no error returned, expected:\n%v", expected)
		}
		if err.Error() != expected {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
		}
	})
}

func TestPagerdutyDetails(t *testing.T) {
	tests := []struct {
		in      string
		checkFn func(map[string]any)
	}{
		{
			in: `
routing_key: 'xyz'
`,
			checkFn: func(d map[string]any) {
				if len(d) != 4 {
					t.Errorf("expected 4 items, got: %d", len(d))
				}
			},
		},
		{
			in: `
routing_key: 'xyz'
details:
  key1: val1
`,
			checkFn: func(d map[string]any) {
				if len(d) != 5 {
					t.Errorf("expected 5 items, got: %d", len(d))
				}
			},
		},
		{
			in: `
routing_key: 'xyz'
details:
  key1: val1
  key2: val2
  firing: firing
`,
			checkFn: func(d map[string]any) {
				if len(d) != 6 {
					t.Errorf("expected 6 items, got: %d", len(d))
				}
			},
		},
	}
	for _, tc := range tests {
		var cfg PagerdutyConfig
		err := yaml.UnmarshalStrict([]byte(tc.in), &cfg)
		if err != nil {
			t.Errorf("expected no error, got:%v", err)
		}

		if tc.checkFn != nil {
			tc.checkFn(cfg.Details)
		}
	}
}

func TestPagerDutySource(t *testing.T) {
	for _, tc := range []struct {
		title string
		in    string

		expectedSource string
	}{
		{
			title: "check source field is backward compatible",
			in: `
routing_key: 'xyz'
client: 'alert-manager-client'
`,
			expectedSource: "alert-manager-client",
		},
		{
			title: "check source field is set",
			in: `
routing_key: 'xyz'
client: 'alert-manager-client'
source: 'alert-manager-source'
`,
			expectedSource: "alert-manager-source",
		},
	} {
		t.Run(tc.title, func(t *testing.T) {
			var cfg PagerdutyConfig
			err := yaml.UnmarshalStrict([]byte(tc.in), &cfg)
			require.NoError(t, err)
			require.Equal(t, tc.expectedSource, cfg.Source)
		})
	}
}

func TestWebhookURLIsPresent(t *testing.T) {
	in := `{}`
	var cfg WebhookConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)

	expected := "one of url or url_file must be configured"

	if err == nil {
		t.Fatalf("no error returned, expected:\n%v", expected)
	}
	if err.Error() != expected {
		t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
	}
}

func TestWebhookURLOrURLFile(t *testing.T) {
	in := `
url: 'http://example.com'
url_file: 'http://example.com'
`
	var cfg WebhookConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)

	expected := "at most one of url & url_file must be configured"

	if err == nil {
		t.Fatalf("no error returned, expected:\n%v", expected)
	}
	if err.Error() != expected {
		t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
	}
}

func TestWebhookHttpConfigIsValid(t *testing.T) {
	in := `
url: 'http://example.com'
http_config:
  bearer_token: foo
  bearer_token_file: /tmp/bar
`
	var cfg WebhookConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)

	expected := "at most one of bearer_token & bearer_token_file must be configured"

	if err == nil {
		t.Fatalf("no error returned, expected:\n%v", expected)
	}
	if err.Error() != expected {
		t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
	}
}

func TestWebhookHttpConfigIsOptional(t *testing.T) {
	in := `
url: 'http://example.com'
`
	var cfg WebhookConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)
	if err != nil {
		t.Fatalf("no error expected, returned:\n%v", err.Error())
	}
}

func TestWebhookPasswordIsObfuscated(t *testing.T) {
	in := `
url: 'http://example.com'
http_config:
  basic_auth:
    username: foo
    password: supersecret
`
	var cfg WebhookConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)
	if err != nil {
		t.Fatalf("no error expected, returned:\n%v", err.Error())
	}

	ycfg, err := yaml.Marshal(cfg)
	if err != nil {
		t.Fatalf("no error expected, returned:\n%v", err.Error())
	}
	if strings.Contains(string(ycfg), "supersecret") {
		t.Errorf("Found password in the YAML cfg: %s\n", ycfg)
	}
}

func TestVictorOpsConfiguration(t *testing.T) {
	t.Run("valid configuration", func(t *testing.T) {
		in := `
routing_key: test
api_key_file: /global_file
`
		var cfg VictorOpsConfig
		err := yaml.UnmarshalStrict([]byte(in), &cfg)
		if err != nil {
			t.Fatalf("no error was expected:\n%v", err)
		}
	})

	t.Run("routing key is missing", func(t *testing.T) {
		in := `
routing_key: ''
`
		var cfg VictorOpsConfig
		err := yaml.UnmarshalStrict([]byte(in), &cfg)

		expected := "missing Routing key in VictorOps config"

		if err == nil {
			t.Fatalf("no error returned, expected:\n%v", expected)
		}
		if err.Error() != expected {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
		}
	})

	t.Run("api_key and api_key_file both defined", func(t *testing.T) {
		in := `
routing_key: test
api_key: xyz
api_key_file: /global_file
`
		var cfg VictorOpsConfig
		err := yaml.UnmarshalStrict([]byte(in), &cfg)

		expected := "at most one of api_key & api_key_file must be configured"

		if err == nil {
			t.Fatalf("no error returned, expected:\n%v", expected)
		}
		if err.Error() != expected {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
		}
	})
}

func TestVictorOpsCustomFieldsValidation(t *testing.T) {
	in := `
routing_key: 'test'
custom_fields:
  entity_state: 'state_message'
`
	var cfg VictorOpsConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)

	expected := "victorOps config contains custom field entity_state which cannot be used as it conflicts with the fixed/static fields"

	if err == nil {
		t.Fatalf("no error returned, expected:\n%v", expected)
	}
	if err.Error() != expected {
		t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
	}

	in = `
routing_key: 'test'
custom_fields:
  my_special_field: 'special_label'
`

	err = yaml.UnmarshalStrict([]byte(in), &cfg)

	expected = "special_label"

	if err != nil {
		t.Fatalf("Unexpected error returned, got:\n%v", err.Error())
	}

	val, ok := cfg.CustomFields["my_special_field"]

	if !ok {
		t.Fatalf("Expected Custom Field to have value %v set, field is empty", expected)
	}
	if val != expected {
		t.Errorf("\nexpected custom field my_special_field value:\n%v\ngot:\n%v", expected, val)
	}
}

func TestPushoverUserKeyIsPresent(t *testing.T) {
	in := `
user_key: ''
`
	var cfg PushoverConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)

	expected := "one of user_key or user_key_file must be configured"

	if err == nil {
		t.Fatalf("no error returned, expected:\n%v", expected)
	}
	if err.Error() != expected {
		t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
	}
}

func TestPushoverUserKeyOrUserKeyFile(t *testing.T) {
	in := `
user_key: 'user key'
user_key_file: /pushover/user_key
`
	var cfg PushoverConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)

	expected := "at most one of user_key & user_key_file must be configured"

	if err == nil {
		t.Fatalf("no error returned, expected:\n%v", expected)
	}
	if err.Error() != expected {
		t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
	}
}

func TestPushoverTokenIsPresent(t *testing.T) {
	in := `
user_key: '<user_key>'
token: ''
`
	var cfg PushoverConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)

	expected := "one of token or token_file must be configured"

	if err == nil {
		t.Fatalf("no error returned, expected:\n%v", expected)
	}
	if err.Error() != expected {
		t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
	}
}

func TestPushoverTokenOrTokenFile(t *testing.T) {
	in := `
token: 'pushover token'
token_file: /pushover/token
user_key: 'user key'
`
	var cfg PushoverConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)

	expected := "at most one of token & token_file must be configured"

	if err == nil {
		t.Fatalf("no error returned, expected:\n%v", expected)
	}
	if err.Error() != expected {
		t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
	}
}

func TestPushoverHTMLOrMonospace(t *testing.T) {
	in := `
token: 'pushover token'
user_key: 'user key'
html: true
monospace: true
`
	var cfg PushoverConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)

	expected := "at most one of monospace & html must be configured"

	if err == nil {
		t.Fatalf("no error returned, expected:\n%v", expected)
	}
	if err.Error() != expected {
		t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
	}
}

func TestLoadSlackConfiguration(t *testing.T) {
	tests := []struct {
		in       string
		expected SlackConfig
	}{
		{
			in: `
color: green
username: mark
channel: engineering
title_link: http://example.com/
image_url: https://example.com/logo.png
`,
			expected: SlackConfig{
				Color: "green", Username: "mark", Channel: "engineering",
				TitleLink: "http://example.com/",
				ImageURL:  "https://example.com/logo.png",
			},
		},
		{
			in: `
color: green
username: mark
channel: alerts
title_link: http://example.com/alert1
mrkdwn_in:
- pretext
- text
`,
			expected: SlackConfig{
				Color: "green", Username: "mark", Channel: "alerts",
				MrkdwnIn: []string{"pretext", "text"}, TitleLink: "http://example.com/alert1",
			},
		},
	}
	for _, rt := range tests {
		var cfg SlackConfig
		err := yaml.UnmarshalStrict([]byte(rt.in), &cfg)
		if err != nil {
			t.Fatalf("\nerror returned when none expected, error:\n%v", err)
		}
		if rt.expected.Color != cfg.Color {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", rt.expected.Color, cfg.Color)
		}
		if rt.expected.Username != cfg.Username {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", rt.expected.Username, cfg.Username)
		}
		if rt.expected.Channel != cfg.Channel {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", rt.expected.Channel, cfg.Channel)
		}
		if rt.expected.ThumbURL != cfg.ThumbURL {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", rt.expected.ThumbURL, cfg.ThumbURL)
		}
		if rt.expected.TitleLink != cfg.TitleLink {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", rt.expected.TitleLink, cfg.TitleLink)
		}
		if rt.expected.ImageURL != cfg.ImageURL {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", rt.expected.ImageURL, cfg.ImageURL)
		}
		if len(rt.expected.MrkdwnIn) != len(cfg.MrkdwnIn) {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", rt.expected.MrkdwnIn, cfg.MrkdwnIn)
		}
		for i := range cfg.MrkdwnIn {
			if rt.expected.MrkdwnIn[i] != cfg.MrkdwnIn[i] {
				t.Errorf("\nexpected:\n%v\ngot:\n%v\nat index %v", rt.expected.MrkdwnIn[i], cfg.MrkdwnIn[i], i)
			}
		}
	}
}

func TestSlackAuthMethodConfigValidation(t *testing.T) {
	tests := []struct {
		in          string
		expectedErr string
	}{
		{
			in: `
api_url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX'
api_url_file: /slack_url
`,
			expectedErr: "at most one of api_url & api_url_file must be configured",
		},
		{
			in: `
app_token: 'xoxb-1234-abcdefgh'
app_token_file: /slack_app_token
`,
			expectedErr: "at most one of app_token & app_token_file must be configured",
		},
		{
			in: `
app_token: 'xoxb-1234-abcdefgh'
api_url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX'
`,
			expectedErr: "at most one of api_url/api_url_file & app_token/app_token_file must be configured",
		},
	}

	for _, rt := range tests {
		var cfg SlackConfig
		err := yaml.UnmarshalStrict([]byte(rt.in), &cfg)

		// Check if an error occurred when it was NOT expected to.
		if rt.expectedErr == "" && err != nil {
			t.Fatalf("\nerror returned when none expected, error:\n%v", err)
		}
		// Check that an error occurred if one was expected to.
		if rt.expectedErr != "" && err == nil {
			t.Fatalf("\nno error returned, expected:\n%v", rt.expectedErr)
		}
		// Check that the error that occurred was what was expected.
		if err != nil && err.Error() != rt.expectedErr {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", rt.expectedErr, err.Error())
		}
	}
}

func TestSlackFieldConfigValidation(t *testing.T) {
	tests := []struct {
		in       string
		expected string
	}{
		{
			in: `
fields:
- title: first
  value: hello
- title: second
`,
			expected: "missing value in Slack field configuration",
		},
		{
			in: `
fields:
- title: first
  value: hello
  short: true
- value: world
  short: true
`,
			expected: "missing title in Slack field configuration",
		},
		{
			in: `
fields:
- title: first
  value: hello
  short: true
- title: second
  value: world
`,
			expected: "",
		},
	}

	for _, rt := range tests {
		var cfg SlackConfig
		err := yaml.UnmarshalStrict([]byte(rt.in), &cfg)

		// Check if an error occurred when it was NOT expected to.
		if rt.expected == "" && err != nil {
			t.Fatalf("\nerror returned when none expected, error:\n%v", err)
		}
		// Check that an error occurred if one was expected to.
		if rt.expected != "" && err == nil {
			t.Fatalf("\nno error returned, expected:\n%v", rt.expected)
		}
		// Check that the error that occurred was what was expected.
		if err != nil && err.Error() != rt.expected {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", rt.expected, err.Error())
		}
	}
}

func TestSlackFieldConfigUnmarshaling(t *testing.T) {
	in := `
fields:
- title: first
  value: hello
  short: true
- title: second
  value: world
- title: third
  value: slack field test
  short: false
`
	expected := []*SlackField{
		{
			Title: "first",
			Value: "hello",
			Short: newBoolPointer(true),
		},
		{
			Title: "second",
			Value: "world",
			Short: nil,
		},
		{
			Title: "third",
			Value: "slack field test",
			Short: newBoolPointer(false),
		},
	}

	var cfg SlackConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)
	if err != nil {
		t.Fatalf("\nerror returned when none expected, error:\n%v", err)
	}

	for index, field := range cfg.Fields {
		exp := expected[index]
		if field.Title != exp.Title {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Title, field.Title)
		}
		if field.Value != exp.Value {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Value, field.Value)
		}
		if exp.Short == nil && field.Short != nil {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Short, *field.Short)
		}
		if exp.Short != nil && field.Short == nil {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", *exp.Short, field.Short)
		}
		if exp.Short != nil && *exp.Short != *field.Short {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", *exp.Short, *field.Short)
		}
	}
}

func TestSlackActionsValidation(t *testing.T) {
	in := `
actions:
- type: button
  text: hello
  url: https://localhost
  style: danger
- type: button
  text: hello
  name: something
  style: default
  confirm:
    title: please confirm
    text: are you sure?
    ok_text: yes
    dismiss_text: no
`
	expected := []*SlackAction{
		{
			Type:  "button",
			Text:  "hello",
			URL:   "https://localhost",
			Style: "danger",
		},
		{
			Type:  "button",
			Text:  "hello",
			Name:  "something",
			Style: "default",
			ConfirmField: &SlackConfirmationField{
				Title:       "please confirm",
				Text:        "are you sure?",
				OkText:      "yes",
				DismissText: "no",
			},
		},
	}

	var cfg SlackConfig
	err := yaml.UnmarshalStrict([]byte(in), &cfg)
	if err != nil {
		t.Fatalf("\nerror returned when none expected, error:\n%v", err)
	}

	for index, action := range cfg.Actions {
		exp := expected[index]
		if action.Type != exp.Type {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Type, action.Type)
		}
		if action.Text != exp.Text {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Text, action.Text)
		}
		if action.URL != exp.URL {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.URL, action.URL)
		}
		if action.Style != exp.Style {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Style, action.Style)
		}
		if action.Name != exp.Name {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Name, action.Name)
		}
		if action.Value != exp.Value {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.Value, action.Value)
		}
		if action.ConfirmField != nil && exp.ConfirmField == nil || action.ConfirmField == nil && exp.ConfirmField != nil {
			t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.ConfirmField, action.ConfirmField)
		} else if action.ConfirmField != nil && exp.ConfirmField != nil {
			if action.ConfirmField.Title != exp.ConfirmField.Title {
				t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.ConfirmField.Title, action.ConfirmField.Title)
			}
			if action.ConfirmField.Text != exp.ConfirmField.Text {
				t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.ConfirmField.Text, action.ConfirmField.Text)
			}
			if action.ConfirmField.OkText != exp.ConfirmField.OkText {
				t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.ConfirmField.OkText, action.ConfirmField.OkText)
			}
			if action.ConfirmField.DismissText != exp.ConfirmField.DismissText {
				t.Errorf("\nexpected:\n%v\ngot:\n%v", exp.ConfirmField.DismissText, action.ConfirmField.DismissText)
			}
		}
	}
}

func TestOpsgenieTypeMatcher(t *testing.T) {
	good := []string{"team", "user", "escalation", "schedule"}
	for _, g := range good {
		if !opsgenieTypeMatcher.MatchString(g) {
			t.Fatalf("failed to match with %s", g)
		}
	}
	bad := []string{"0user", "team1", "2escalation3", "sche4dule", "User", "TEAM"}
	for _, b := range bad {
		if opsgenieTypeMatcher.MatchString(b) {
			t.Errorf("mistakenly match with %s", b)
		}
	}
}

func TestOpsGenieConfiguration(t *testing.T) {
	for _, tc := range []struct {
		name string
		in   string

		err bool
	}{
		{
			name: "valid configuration",
			in: `api_key: xyz
responders:
- id: foo
  type: scheDule
- name: bar
  type: teams
- username: fred
  type: USER
api_url: http://example.com
`,
		},
		{
			name: "api_key and api_key_file both defined",
			in: `api_key: xyz
api_key_file: xyz
api_url: http://example.com
`,
			err: true,
		},
		{
			name: "invalid responder type",
			in: `api_key: xyz
responders:
- id: foo
  type: wrong
api_url: http://example.com
`,
			err: true,
		},
		{
			name: "missing responder field",
			in: `api_key: xyz
responders:
- type: schedule
api_url: http://example.com
`,
			err: true,
		},
		{
			name: "valid responder type template",
			in: `api_key: xyz
responders:
- id: foo
  type: "{{/* valid comment */}}team"
api_url: http://example.com
`,
		},
		{
			name: "invalid responder type template",
			in: `api_key: xyz
responders:
- id: foo
  type: "{{/* invalid comment }}team"
api_url: http://example.com
`,
			err: true,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			var cfg OpsGenieConfig

			err := yaml.UnmarshalStrict([]byte(tc.in), &cfg)
			if tc.err {
				if err == nil {
					t.Fatalf("expected error but got none")
				}
				return
			}

			if err != nil {
				t.Errorf("expected no error, got %v", err)
			}
		})
	}
}

func TestSNS(t *testing.T) {
	for _, tc := range []struct {
		in  string
		err bool
	}{
		{
			// Valid configuration without sigv4.
			in:  `target_arn: target`,
			err: false,
		},
		{
			// Valid configuration without sigv4.
			in:  `topic_arn: topic`,
			err: false,
		},
		{
			// Valid configuration with sigv4.
			in: `phone_number: phone
sigv4:
    access_key: abc
    secret_key: abc
`,
			err: false,
		},
		{
			// at most one of 'target_arn', 'topic_arn' or 'phone_number' must be provided without sigv4.
			in: `topic_arn: topic
target_arn: target
`,
			err: true,
		},
		{
			// at most one of 'target_arn', 'topic_arn' or 'phone_number' must be provided without sigv4.
			in: `topic_arn: topic
phone_number: phone
`,
			err: true,
		},
		{
			// one of 'target_arn', 'topic_arn' or 'phone_number' must be provided without sigv4.
			in:  "{}",
			err: true,
		},
		{
			// one of 'target_arn', 'topic_arn' or 'phone_number' must be provided with sigv4.
			in: `sigv4:
    access_key: abc
    secret_key: abc
`,
			err: true,
		},
		{
			// 'secret_key' must be provided with 'access_key'.
			in: `topic_arn: topic
sigv4:
    access_key: abc
`,
			err: true,
		},
		{
			// 'access_key' must be provided with 'secret_key'.
			in: `topic_arn: topic
sigv4:
    secret_key: abc
`,
			err: true,
		},
	} {
		t.Run("", func(t *testing.T) {
			var cfg SNSConfig
			err := yaml.UnmarshalStrict([]byte(tc.in), &cfg)
			if err != nil {
				if !tc.err {
					t.Errorf("expecting no error, got %q", err)
				}
				return
			}

			if tc.err {
				t.Logf("%#v", cfg)
				t.Error("expecting error, got none")
			}
		})
	}
}

func TestWeChatTypeMatcher(t *testing.T) {
	good := []string{"text", "markdown"}
	for _, g := range good {
		if !wechatTypeMatcher.MatchString(g) {
			t.Fatalf("failed to match with %s", g)
		}
	}
	bad := []string{"TEXT", "MarkDOwn"}
	for _, b := range bad {
		if wechatTypeMatcher.MatchString(b) {
			t.Errorf("mistakenly match with %s", b)
		}
	}
}

func TestWebexConfiguration(t *testing.T) {
	tc := []struct {
		name string

		in       string
		expected error
	}{
		{
			name: "with no room_id - it fails",
			in: `
message: xyz123
`,
			expected: errors.New("missing room_id on webex_config"),
		},
		{
			name: "with room_id and http_config.authorization set - it succeeds",
			in: `
room_id: 2
http_config:
  authorization:
    credentials: "xxxyyyzz"
`,
		},
	}

	for _, tt := range tc {
		t.Run(tt.name, func(t *testing.T) {
			var cfg WebexConfig
			err := yaml.UnmarshalStrict([]byte(tt.in), &cfg)

			require.Equal(t, tt.expected, err)
		})
	}
}

func TestTelegramConfiguration(t *testing.T) {
	tc := []struct {
		name     string
		in       string
		expected error
	}{
		{
			name: "with both bot_token & bot_token_file - it fails",
			in: `
bot_token: xyz
bot_token_file: /file
`,
			expected: errors.New("at most one of bot_token & bot_token_file must be configured"),
		},
		{
			name: "with bot_token and chat_id set - it succeeds",
			in: `
bot_token: xyz
chat_id: 123
`,
		},
		{
			name: "with bot_token, chat_id and message_thread_id set - it succeeds",
			in: `
bot_token: xyz
chat_id: 123
message_thread_id: 456
`,
		},
		{
			name: "with bot_token_file and chat_id set - it succeeds",
			in: `
bot_token_file: /file
chat_id: 123
`,
		},
		{
			name: "with bot_token_file and chat_id_file set - it succeeds",
			in: `
bot_token_file: /file
chat_id_file: /chat_id_file
`,
		},
		{
			name: "with no chat_id set - it fails",
			in: `
bot_token: xyz
`,
			expected: errors.New("missing chat_id or chat_id_file on telegram_config"),
		},
		{
			name: "with both chat_id and chat_id_file - it fails",
			in: `
bot_token: xyz
chat_id: 123
chat_id_file: /file
`,
			expected: errors.New("at most one of chat_id & chat_id_file must be configured"),
		},
		{
			name: "with unknown parse_mode - it fails",
			in: `
bot_token: xyz
chat_id: 123
parse_mode: invalid
`,
			expected: errors.New("unknown parse_mode on telegram_config, must be Markdown, MarkdownV2, HTML or empty string"),
		},
	}

	for _, tt := range tc {
		t.Run(tt.name, func(t *testing.T) {
			var cfg TelegramConfig
			err := yaml.UnmarshalStrict([]byte(tt.in), &cfg)

			require.Equal(t, tt.expected, err)
		})
	}
}

func newBoolPointer(b bool) *bool {
	return &b
}

func TestMattermostField_UnmarshalYAML(t *testing.T) {
	mf := []struct {
		name     string
		in       string
		expected error
	}{
		{
			name: "with title, value and short - it succeeds",
			in: `
title: some title
value: some value
short: true
`,
		},
		{
			name: "with title and value - it succeeds",
			in: `
title: some title
value: some value
`,
		},
		{
			name: "with no value - it fails",
			in: `
title: some title
`,
			expected: errors.New("missing value in Mattermost field configuration"),
		},
		{
			name: "with no title - it fails",
			in: `
value: some value
`,
			expected: errors.New("missing title in Mattermost field configuration"),
		},
	}

	for _, tt := range mf {
		t.Run(tt.name, func(t *testing.T) {
			var cfg MattermostField
			err := yaml.UnmarshalStrict([]byte(tt.in), &cfg)

			require.Equal(t, tt.expected, err)
		})
	}
}

func TestMattermostConfig_UnmarshalYAML(t *testing.T) {
	mc := []struct {
		name     string
		in       string
		expected error
	}{
		{
			name: "with url and text - it succeeds",
			in: `
webhook_url: http://some.url
channel: some_channel
username: some_username
text: some text
`,
		},
		{
			name: "with url_file, attachments and props - it succeeds",
			in: `
webhook_url_file: /some/url.file
channel: some_channel
username: some_username
attachments:
- text: some text
props:
  card: some text
`,
		},
		{
			name: "with url and url_file - it fails",
			in: `
webhook_url: http://some.url
webhook_url_file: /some/url.file
channel: some_channel
username: some_username
attachments:
- text: some text
`,
			expected: errors.New("at most one of webhook_url & webhook_url_file must be configured"),
		},
		{
			name: "with text and attachments - it succeeds",
			in: `
webhook_url: http://some.url
channel: some_channel
username: some_username
text: some text
attachments:
- text: some text
`,
		},
	}

	for _, tt := range mc {
		t.Run(tt.name, func(t *testing.T) {
			var cfg MattermostConfig
			err := yaml.UnmarshalStrict([]byte(tt.in), &cfg)

			require.Equal(t, tt.expected, err)
		})
	}
}

func TestEmailConfig_UnmarshalYAML(t *testing.T) {
	testConfig := []struct {
		name     string
		in       string
		expected error
	}{
		{
			name: "with basic config - it succeeds",
			in: `
to: foobar@example.com
headers: {X-Custom-Header: CustomValue}
`,
		},
		{
			name: "with empty to address - it fails",
			in: `
to: ''`,
			expected: errors.New("missing to address in email config"),
		},
		{
			name: "with correct threading - it succeeds",
			in: `
to: foobar@example.com
threading:
  enabled: true
  thread_by_date: daily
`,
		},
		{
			name: "with invalid threading - it fails",
			in: `
to: foobar@example.com
threading:
  enabled: true
  thread_by_date: weekly
`,
			expected: errors.New("threading.thread_by_date must be either 'none' or 'daily'"),
		},
		{
			name: "with duplicate headers - it failes",
			in: `
to: foobar@example.com
headers: {X-Custom-Header: CustomValue, X-CUSTOM-HEADER: AnotherValue}
`,
			expected: errors.New("duplicate header \"X-Custom-Header\" in email config"),
		},
	}

	for _, tt := range testConfig {
		t.Run(tt.name, func(t *testing.T) {
			var cfg EmailConfig
			err := yaml.UnmarshalStrict([]byte(tt.in), &cfg)

			require.Equal(t, tt.expected, err)
		})
	}
}
