package settings_test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"
	"testing"

	validation "github.com/go-ozzo/ozzo-validation/v4"
	"github.com/pocketbase/pocketbase/models/settings"
	"github.com/pocketbase/pocketbase/tools/auth"
	"github.com/pocketbase/pocketbase/tools/mailer"
)

func TestSettingsValidate(t *testing.T) {
	s := settings.New()

	// set invalid settings data
	s.Meta.AppName = ""
	s.Logs.MaxDays = -10
	s.Smtp.Enabled = true
	s.Smtp.Host = ""
	s.S3.Enabled = true
	s.S3.Endpoint = "invalid"
	s.AdminAuthToken.Duration = -10
	s.AdminPasswordResetToken.Duration = -10
	s.AdminFileToken.Duration = -10
	s.RecordAuthToken.Duration = -10
	s.RecordPasswordResetToken.Duration = -10
	s.RecordEmailChangeToken.Duration = -10
	s.RecordVerificationToken.Duration = -10
	s.RecordFileToken.Duration = -10
	s.GoogleAuth.Enabled = true
	s.GoogleAuth.ClientId = ""
	s.FacebookAuth.Enabled = true
	s.FacebookAuth.ClientId = ""
	s.GithubAuth.Enabled = true
	s.GithubAuth.ClientId = ""
	s.GitlabAuth.Enabled = true
	s.GitlabAuth.ClientId = ""
	s.DiscordAuth.Enabled = true
	s.DiscordAuth.ClientId = ""
	s.TwitterAuth.Enabled = true
	s.TwitterAuth.ClientId = ""
	s.MicrosoftAuth.Enabled = true
	s.MicrosoftAuth.ClientId = ""
	s.SpotifyAuth.Enabled = true
	s.SpotifyAuth.ClientId = ""
	s.KakaoAuth.Enabled = true
	s.KakaoAuth.ClientId = ""
	s.TwitchAuth.Enabled = true
	s.TwitchAuth.ClientId = ""
	s.StravaAuth.Enabled = true
	s.StravaAuth.ClientId = ""
	s.GiteeAuth.Enabled = true
	s.GiteeAuth.ClientId = ""
	s.LivechatAuth.Enabled = true
	s.LivechatAuth.ClientId = ""
	s.GiteaAuth.Enabled = true
	s.GiteaAuth.ClientId = ""
	s.OIDCAuth.Enabled = true
	s.OIDCAuth.ClientId = ""
	s.OIDC2Auth.Enabled = true
	s.OIDC2Auth.ClientId = ""
	s.OIDC3Auth.Enabled = true
	s.OIDC3Auth.ClientId = ""
	s.AppleAuth.Enabled = true
	s.AppleAuth.ClientId = ""
	s.InstagramAuth.Enabled = true
	s.InstagramAuth.ClientId = ""
	s.VKAuth.Enabled = true
	s.VKAuth.ClientId = ""
	s.YandexAuth.Enabled = true
	s.YandexAuth.ClientId = ""

	// check if Validate() is triggering the members validate methods.
	err := s.Validate()
	if err == nil {
		t.Fatalf("Expected error, got nil")
	}

	expectations := []string{
		`"meta":{`,
		`"logs":{`,
		`"smtp":{`,
		`"s3":{`,
		`"adminAuthToken":{`,
		`"adminPasswordResetToken":{`,
		`"adminFileToken":{`,
		`"recordAuthToken":{`,
		`"recordPasswordResetToken":{`,
		`"recordEmailChangeToken":{`,
		`"recordVerificationToken":{`,
		`"recordFileToken":{`,
		`"googleAuth":{`,
		`"facebookAuth":{`,
		`"githubAuth":{`,
		`"gitlabAuth":{`,
		`"discordAuth":{`,
		`"twitterAuth":{`,
		`"microsoftAuth":{`,
		`"spotifyAuth":{`,
		`"kakaoAuth":{`,
		`"twitchAuth":{`,
		`"stravaAuth":{`,
		`"giteeAuth":{`,
		`"livechatAuth":{`,
		`"giteaAuth":{`,
		`"oidcAuth":{`,
		`"oidc2Auth":{`,
		`"oidc3Auth":{`,
		`"appleAuth":{`,
		`"instagramAuth":{`,
		`"vkAuth":{`,
		`"yandexAuth":{`,
	}

	errBytes, _ := json.Marshal(err)
	jsonErr := string(errBytes)
	for _, expected := range expectations {
		if !strings.Contains(jsonErr, expected) {
			t.Errorf("Expected error key %s in %v", expected, jsonErr)
		}
	}
}

func TestSettingsMerge(t *testing.T) {
	s1 := settings.New()
	s1.Meta.AppUrl = "old_app_url"

	s2 := settings.New()
	s2.Meta.AppName = "test"
	s2.Logs.MaxDays = 123
	s2.Smtp.Host = "test"
	s2.Smtp.Enabled = true
	s2.S3.Enabled = true
	s2.S3.Endpoint = "test"
	s2.Backups.Cron = "* * * * *"
	s2.AdminAuthToken.Duration = 1
	s2.AdminPasswordResetToken.Duration = 2
	s2.AdminFileToken.Duration = 2
	s2.RecordAuthToken.Duration = 3
	s2.RecordPasswordResetToken.Duration = 4
	s2.RecordEmailChangeToken.Duration = 5
	s2.RecordVerificationToken.Duration = 6
	s2.RecordFileToken.Duration = 7
	s2.GoogleAuth.Enabled = true
	s2.GoogleAuth.ClientId = "google_test"
	s2.FacebookAuth.Enabled = true
	s2.FacebookAuth.ClientId = "facebook_test"
	s2.GithubAuth.Enabled = true
	s2.GithubAuth.ClientId = "github_test"
	s2.GitlabAuth.Enabled = true
	s2.GitlabAuth.ClientId = "gitlab_test"
	s2.DiscordAuth.Enabled = true
	s2.DiscordAuth.ClientId = "discord_test"
	s2.TwitterAuth.Enabled = true
	s2.TwitterAuth.ClientId = "twitter_test"
	s2.MicrosoftAuth.Enabled = true
	s2.MicrosoftAuth.ClientId = "microsoft_test"
	s2.SpotifyAuth.Enabled = true
	s2.SpotifyAuth.ClientId = "spotify_test"
	s2.KakaoAuth.Enabled = true
	s2.KakaoAuth.ClientId = "kakao_test"
	s2.TwitchAuth.Enabled = true
	s2.TwitchAuth.ClientId = "twitch_test"
	s2.StravaAuth.Enabled = true
	s2.StravaAuth.ClientId = "strava_test"
	s2.GiteeAuth.Enabled = true
	s2.GiteeAuth.ClientId = "gitee_test"
	s2.LivechatAuth.Enabled = true
	s2.LivechatAuth.ClientId = "livechat_test"
	s2.GiteaAuth.Enabled = true
	s2.GiteaAuth.ClientId = "gitea_test"
	s2.OIDCAuth.Enabled = true
	s2.OIDCAuth.ClientId = "oidc_test"
	s2.OIDC2Auth.Enabled = true
	s2.OIDC2Auth.ClientId = "oidc2_test"
	s2.OIDC3Auth.Enabled = true
	s2.OIDC3Auth.ClientId = "oidc3_test"
	s2.AppleAuth.Enabled = true
	s2.AppleAuth.ClientId = "apple_test"
	s2.InstagramAuth.Enabled = true
	s2.InstagramAuth.ClientId = "instagram_test"
	s2.VKAuth.Enabled = true
	s2.VKAuth.ClientId = "vk_test"
	s2.YandexAuth.Enabled = true
	s2.YandexAuth.ClientId = "yandex_test"

	if err := s1.Merge(s2); err != nil {
		t.Fatal(err)
	}

	s1Encoded, err := json.Marshal(s1)
	if err != nil {
		t.Fatal(err)
	}

	s2Encoded, err := json.Marshal(s2)
	if err != nil {
		t.Fatal(err)
	}

	if string(s1Encoded) != string(s2Encoded) {
		t.Fatalf("Expected the same serialization, got %v VS %v", string(s1Encoded), string(s2Encoded))
	}
}

func TestSettingsClone(t *testing.T) {
	s1 := settings.New()

	s2, err := s1.Clone()
	if err != nil {
		t.Fatal(err)
	}

	s1Bytes, err := json.Marshal(s1)
	if err != nil {
		t.Fatal(err)
	}

	s2Bytes, err := json.Marshal(s2)
	if err != nil {
		t.Fatal(err)
	}

	if string(s1Bytes) != string(s2Bytes) {
		t.Fatalf("Expected equivalent serialization, got %v VS %v", string(s1Bytes), string(s2Bytes))
	}

	// verify that it is a deep copy
	s1.Meta.AppName = "new"
	if s1.Meta.AppName == s2.Meta.AppName {
		t.Fatalf("Expected s1 and s2 to have different Meta.AppName, got %s", s1.Meta.AppName)
	}
}

func TestSettingsRedactClone(t *testing.T) {
	testSecret := "test_secret"

	s1 := settings.New()

	// control fields
	s1.Meta.AppName = "test123"

	// secrets
	s1.Smtp.Password = testSecret
	s1.S3.Secret = testSecret
	s1.Backups.S3.Secret = testSecret
	s1.AdminAuthToken.Secret = testSecret
	s1.AdminPasswordResetToken.Secret = testSecret
	s1.AdminFileToken.Secret = testSecret
	s1.RecordAuthToken.Secret = testSecret
	s1.RecordPasswordResetToken.Secret = testSecret
	s1.RecordEmailChangeToken.Secret = testSecret
	s1.RecordVerificationToken.Secret = testSecret
	s1.RecordFileToken.Secret = testSecret
	s1.GoogleAuth.ClientSecret = testSecret
	s1.FacebookAuth.ClientSecret = testSecret
	s1.GithubAuth.ClientSecret = testSecret
	s1.GitlabAuth.ClientSecret = testSecret
	s1.DiscordAuth.ClientSecret = testSecret
	s1.TwitterAuth.ClientSecret = testSecret
	s1.MicrosoftAuth.ClientSecret = testSecret
	s1.SpotifyAuth.ClientSecret = testSecret
	s1.KakaoAuth.ClientSecret = testSecret
	s1.TwitchAuth.ClientSecret = testSecret
	s1.StravaAuth.ClientSecret = testSecret
	s1.GiteeAuth.ClientSecret = testSecret
	s1.LivechatAuth.ClientSecret = testSecret
	s1.GiteaAuth.ClientSecret = testSecret
	s1.OIDCAuth.ClientSecret = testSecret
	s1.OIDC2Auth.ClientSecret = testSecret
	s1.OIDC3Auth.ClientSecret = testSecret
	s1.AppleAuth.ClientSecret = testSecret
	s1.InstagramAuth.ClientSecret = testSecret
	s1.VKAuth.ClientSecret = testSecret
	s1.YandexAuth.ClientSecret = testSecret

	s1Bytes, err := json.Marshal(s1)
	if err != nil {
		t.Fatal(err)
	}

	s2, err := s1.RedactClone()
	if err != nil {
		t.Fatal(err)
	}

	s2Bytes, err := json.Marshal(s2)
	if err != nil {
		t.Fatal(err)
	}

	if bytes.Equal(s1Bytes, s2Bytes) {
		t.Fatalf("Expected the 2 settings to differ, got \n%s", s2Bytes)
	}

	if strings.Contains(string(s2Bytes), testSecret) {
		t.Fatalf("Expected %q secret to be replaced with mask, got \n%s", testSecret, s2Bytes)
	}

	if !strings.Contains(string(s2Bytes), settings.SecretMask) {
		t.Fatalf("Expected the secrets to be replaced with the secret mask, got \n%s", s2Bytes)
	}

	if !strings.Contains(string(s2Bytes), `"appName":"test123"`) {
		t.Fatalf("Missing control field in \n%s", s2Bytes)
	}
}

func TestNamedAuthProviderConfigs(t *testing.T) {
	s := settings.New()

	s.GoogleAuth.ClientId = "google_test"
	s.FacebookAuth.ClientId = "facebook_test"
	s.GithubAuth.ClientId = "github_test"
	s.GitlabAuth.ClientId = "gitlab_test"
	s.GitlabAuth.Enabled = true // control
	s.DiscordAuth.ClientId = "discord_test"
	s.TwitterAuth.ClientId = "twitter_test"
	s.MicrosoftAuth.ClientId = "microsoft_test"
	s.SpotifyAuth.ClientId = "spotify_test"
	s.KakaoAuth.ClientId = "kakao_test"
	s.TwitchAuth.ClientId = "twitch_test"
	s.StravaAuth.ClientId = "strava_test"
	s.GiteeAuth.ClientId = "gitee_test"
	s.LivechatAuth.ClientId = "livechat_test"
	s.GiteaAuth.ClientId = "gitea_test"
	s.OIDCAuth.ClientId = "oidc_test"
	s.OIDC2Auth.ClientId = "oidc2_test"
	s.OIDC3Auth.ClientId = "oidc3_test"
	s.AppleAuth.ClientId = "apple_test"
	s.InstagramAuth.ClientId = "instagram_test"
	s.VKAuth.ClientId = "vk_test"
	s.YandexAuth.ClientId = "yandex_test"

	result := s.NamedAuthProviderConfigs()

	encoded, err := json.Marshal(result)
	if err != nil {
		t.Fatal(err)
	}
	encodedStr := string(encoded)

	expectedParts := []string{
		`"discord":{"enabled":false,"clientId":"discord_test"`,
		`"facebook":{"enabled":false,"clientId":"facebook_test"`,
		`"github":{"enabled":false,"clientId":"github_test"`,
		`"gitlab":{"enabled":true,"clientId":"gitlab_test"`,
		`"google":{"enabled":false,"clientId":"google_test"`,
		`"microsoft":{"enabled":false,"clientId":"microsoft_test"`,
		`"spotify":{"enabled":false,"clientId":"spotify_test"`,
		`"twitter":{"enabled":false,"clientId":"twitter_test"`,
		`"kakao":{"enabled":false,"clientId":"kakao_test"`,
		`"twitch":{"enabled":false,"clientId":"twitch_test"`,
		`"strava":{"enabled":false,"clientId":"strava_test"`,
		`"gitee":{"enabled":false,"clientId":"gitee_test"`,
		`"livechat":{"enabled":false,"clientId":"livechat_test"`,
		`"gitea":{"enabled":false,"clientId":"gitea_test"`,
		`"oidc":{"enabled":false,"clientId":"oidc_test"`,
		`"oidc2":{"enabled":false,"clientId":"oidc2_test"`,
		`"oidc3":{"enabled":false,"clientId":"oidc3_test"`,
		`"apple":{"enabled":false,"clientId":"apple_test"`,
		`"instagram":{"enabled":false,"clientId":"instagram_test"`,
		`"vk":{"enabled":false,"clientId":"vk_test"`,
		`"yandex":{"enabled":false,"clientId":"yandex_test"`,
	}
	for _, p := range expectedParts {
		if !strings.Contains(encodedStr, p) {
			t.Fatalf("Expected \n%s \nin \n%s", p, encodedStr)
		}
	}
}

func TestTokenConfigValidate(t *testing.T) {
	scenarios := []struct {
		config      settings.TokenConfig
		expectError bool
	}{
		// zero values
		{
			settings.TokenConfig{},
			true,
		},
		// invalid data
		{
			settings.TokenConfig{
				Secret:   strings.Repeat("a", 5),
				Duration: 4,
			},
			true,
		},
		// valid secret but invalid duration
		{
			settings.TokenConfig{
				Secret:   strings.Repeat("a", 30),
				Duration: 63072000 + 1,
			},
			true,
		},
		// valid data
		{
			settings.TokenConfig{
				Secret:   strings.Repeat("a", 30),
				Duration: 100,
			},
			false,
		},
	}

	for i, scenario := range scenarios {
		result := scenario.config.Validate()

		if result != nil && !scenario.expectError {
			t.Errorf("(%d) Didn't expect error, got %v", i, result)
		}

		if result == nil && scenario.expectError {
			t.Errorf("(%d) Expected error, got nil", i)
		}
	}
}

func TestSmtpConfigValidate(t *testing.T) {
	scenarios := []struct {
		config      settings.SmtpConfig
		expectError bool
	}{
		// zero values (disabled)
		{
			settings.SmtpConfig{},
			false,
		},
		// zero values (enabled)
		{
			settings.SmtpConfig{Enabled: true},
			true,
		},
		// invalid data
		{
			settings.SmtpConfig{
				Enabled: true,
				Host:    "test:test:test",
				Port:    -10,
			},
			true,
		},
		// invalid auth method
		{
			settings.SmtpConfig{
				Enabled:    true,
				Host:       "example.com",
				Port:       100,
				AuthMethod: "example",
			},
			true,
		},
		// valid data (no explicit auth method)
		{
			settings.SmtpConfig{
				Enabled: true,
				Host:    "example.com",
				Port:    100,
				Tls:     true,
			},
			false,
		},
		// valid data (explicit auth method - login)
		{
			settings.SmtpConfig{
				Enabled:    true,
				Host:       "example.com",
				Port:       100,
				AuthMethod: mailer.SmtpAuthLogin,
			},
			false,
		},
	}

	for i, scenario := range scenarios {
		result := scenario.config.Validate()

		if result != nil && !scenario.expectError {
			t.Errorf("(%d) Didn't expect error, got %v", i, result)
		}

		if result == nil && scenario.expectError {
			t.Errorf("(%d) Expected error, got nil", i)
		}
	}
}

func TestS3ConfigValidate(t *testing.T) {
	scenarios := []struct {
		config      settings.S3Config
		expectError bool
	}{
		// zero values (disabled)
		{
			settings.S3Config{},
			false,
		},
		// zero values (enabled)
		{
			settings.S3Config{Enabled: true},
			true,
		},
		// invalid data
		{
			settings.S3Config{
				Enabled:  true,
				Endpoint: "test:test:test",
			},
			true,
		},
		// valid data (url endpoint)
		{
			settings.S3Config{
				Enabled:   true,
				Endpoint:  "https://localhost:8090",
				Bucket:    "test",
				Region:    "test",
				AccessKey: "test",
				Secret:    "test",
			},
			false,
		},
		// valid data (hostname endpoint)
		{
			settings.S3Config{
				Enabled:   true,
				Endpoint:  "example.com",
				Bucket:    "test",
				Region:    "test",
				AccessKey: "test",
				Secret:    "test",
			},
			false,
		},
	}

	for i, scenario := range scenarios {
		result := scenario.config.Validate()

		if result != nil && !scenario.expectError {
			t.Errorf("(%d) Didn't expect error, got %v", i, result)
		}

		if result == nil && scenario.expectError {
			t.Errorf("(%d) Expected error, got nil", i)
		}
	}
}

func TestMetaConfigValidate(t *testing.T) {
	invalidTemplate := settings.EmailTemplate{
		Subject:   "test",
		ActionUrl: "test",
		Body:      "test",
	}

	noPlaceholdersTemplate := settings.EmailTemplate{
		Subject:   "test",
		ActionUrl: "http://example.com",
		Body:      "test",
	}

	withPlaceholdersTemplate := settings.EmailTemplate{
		Subject:   "test",
		ActionUrl: "http://example.com" + settings.EmailPlaceholderToken,
		Body:      "test" + settings.EmailPlaceholderActionUrl,
	}

	scenarios := []struct {
		config      settings.MetaConfig
		expectError bool
	}{
		// zero values
		{
			settings.MetaConfig{},
			true,
		},
		// invalid data
		{
			settings.MetaConfig{
				AppName:                    strings.Repeat("a", 300),
				AppUrl:                     "test",
				SenderName:                 strings.Repeat("a", 300),
				SenderAddress:              "invalid_email",
				VerificationTemplate:       invalidTemplate,
				ResetPasswordTemplate:      invalidTemplate,
				ConfirmEmailChangeTemplate: invalidTemplate,
			},
			true,
		},
		// invalid data (missing required placeholders)
		{
			settings.MetaConfig{
				AppName:                    "test",
				AppUrl:                     "https://example.com",
				SenderName:                 "test",
				SenderAddress:              "test@example.com",
				VerificationTemplate:       noPlaceholdersTemplate,
				ResetPasswordTemplate:      noPlaceholdersTemplate,
				ConfirmEmailChangeTemplate: noPlaceholdersTemplate,
			},
			true,
		},
		// valid data
		{
			settings.MetaConfig{
				AppName:                    "test",
				AppUrl:                     "https://example.com",
				SenderName:                 "test",
				SenderAddress:              "test@example.com",
				VerificationTemplate:       withPlaceholdersTemplate,
				ResetPasswordTemplate:      withPlaceholdersTemplate,
				ConfirmEmailChangeTemplate: withPlaceholdersTemplate,
			},
			false,
		},
	}

	for i, scenario := range scenarios {
		result := scenario.config.Validate()

		if result != nil && !scenario.expectError {
			t.Errorf("(%d) Didn't expect error, got %v", i, result)
		}

		if result == nil && scenario.expectError {
			t.Errorf("(%d) Expected error, got nil", i)
		}
	}
}

func TestBackupsConfigValidate(t *testing.T) {
	scenarios := []struct {
		name           string
		config         settings.BackupsConfig
		expectedErrors []string
	}{
		{
			"zero value",
			settings.BackupsConfig{},
			[]string{},
		},
		{
			"invalid cron",
			settings.BackupsConfig{
				Cron:        "invalid",
				CronMaxKeep: 0,
			},
			[]string{"cron", "cronMaxKeep"},
		},
		{
			"invalid enabled S3",
			settings.BackupsConfig{
				S3: settings.S3Config{
					Enabled: true,
				},
			},
			[]string{"s3"},
		},
		{
			"valid data",
			settings.BackupsConfig{
				S3: settings.S3Config{
					Enabled:   true,
					Endpoint:  "example.com",
					Bucket:    "test",
					Region:    "test",
					AccessKey: "test",
					Secret:    "test",
				},
				Cron:        "*/10 * * * *",
				CronMaxKeep: 1,
			},
			[]string{},
		},
	}

	for _, s := range scenarios {
		result := s.config.Validate()

		// parse errors
		errs, ok := result.(validation.Errors)
		if !ok && result != nil {
			t.Errorf("[%s] Failed to parse errors %v", s.name, result)
			continue
		}

		// check errors
		if len(errs) > len(s.expectedErrors) {
			t.Errorf("[%s] Expected error keys %v, got %v", s.name, s.expectedErrors, errs)
		}
		for _, k := range s.expectedErrors {
			if _, ok := errs[k]; !ok {
				t.Errorf("[%s] Missing expected error key %q in %v", s.name, k, errs)
			}
		}
	}
}

func TestEmailTemplateValidate(t *testing.T) {
	scenarios := []struct {
		emailTemplate  settings.EmailTemplate
		expectedErrors []string
	}{
		// require values
		{
			settings.EmailTemplate{},
			[]string{"subject", "actionUrl", "body"},
		},
		// missing placeholders
		{
			settings.EmailTemplate{
				Subject:   "test",
				ActionUrl: "test",
				Body:      "test",
			},
			[]string{"actionUrl", "body"},
		},
		// valid data
		{
			settings.EmailTemplate{
				Subject:   "test",
				ActionUrl: "test" + settings.EmailPlaceholderToken,
				Body:      "test" + settings.EmailPlaceholderActionUrl,
			},
			[]string{},
		},
	}

	for i, s := range scenarios {
		result := s.emailTemplate.Validate()

		// parse errors
		errs, ok := result.(validation.Errors)
		if !ok && result != nil {
			t.Errorf("(%d) Failed to parse errors %v", i, result)
			continue
		}

		// check errors
		if len(errs) > len(s.expectedErrors) {
			t.Errorf("(%d) Expected error keys %v, got %v", i, s.expectedErrors, errs)
		}
		for _, k := range s.expectedErrors {
			if _, ok := errs[k]; !ok {
				t.Errorf("(%d) Missing expected error key %q in %v", i, k, errs)
			}
		}
	}
}

func TestEmailTemplateResolve(t *testing.T) {
	allPlaceholders := settings.EmailPlaceholderActionUrl + settings.EmailPlaceholderToken + settings.EmailPlaceholderAppName + settings.EmailPlaceholderAppUrl

	scenarios := []struct {
		emailTemplate     settings.EmailTemplate
		expectedSubject   string
		expectedBody      string
		expectedActionUrl string
	}{
		// no placeholders
		{
			emailTemplate: settings.EmailTemplate{
				Subject:   "subject:",
				Body:      "body:",
				ActionUrl: "/actionUrl////",
			},
			expectedSubject:   "subject:",
			expectedActionUrl: "/actionUrl/",
			expectedBody:      "body:",
		},
		// with placeholders
		{
			emailTemplate: settings.EmailTemplate{
				ActionUrl: "/actionUrl////" + allPlaceholders,
				Subject:   "subject:" + allPlaceholders,
				Body:      "body:" + allPlaceholders,
			},
			expectedActionUrl: fmt.Sprintf(
				"/actionUrl/%%7BACTION_URL%%7D%s%s%s",
				"token_test",
				"name_test",
				"url_test",
			),
			expectedSubject: fmt.Sprintf(
				"subject:%s%s%s%s",
				settings.EmailPlaceholderActionUrl,
				settings.EmailPlaceholderToken,
				"name_test",
				"url_test",
			),
			expectedBody: fmt.Sprintf(
				"body:%s%s%s%s",
				fmt.Sprintf(
					"/actionUrl/%%7BACTION_URL%%7D%s%s%s",
					"token_test",
					"name_test",
					"url_test",
				),
				"token_test",
				"name_test",
				"url_test",
			),
		},
	}

	for i, s := range scenarios {
		subject, body, actionUrl := s.emailTemplate.Resolve("name_test", "url_test", "token_test")

		if s.expectedSubject != subject {
			t.Errorf("(%d) Expected subject %q got %q", i, s.expectedSubject, subject)
		}

		if s.expectedBody != body {
			t.Errorf("(%d) Expected body \n%v got \n%v", i, s.expectedBody, body)
		}

		if s.expectedActionUrl != actionUrl {
			t.Errorf("(%d) Expected actionUrl \n%v got \n%v", i, s.expectedActionUrl, actionUrl)
		}
	}
}

func TestLogsConfigValidate(t *testing.T) {
	scenarios := []struct {
		config      settings.LogsConfig
		expectError bool
	}{
		// zero values
		{
			settings.LogsConfig{},
			false,
		},
		// invalid data
		{
			settings.LogsConfig{MaxDays: -10},
			true,
		},
		// valid data
		{
			settings.LogsConfig{MaxDays: 1},
			false,
		},
	}

	for i, scenario := range scenarios {
		result := scenario.config.Validate()

		if result != nil && !scenario.expectError {
			t.Errorf("(%d) Didn't expect error, got %v", i, result)
		}

		if result == nil && scenario.expectError {
			t.Errorf("(%d) Expected error, got nil", i)
		}
	}
}

func TestAuthProviderConfigValidate(t *testing.T) {
	scenarios := []struct {
		config      settings.AuthProviderConfig
		expectError bool
	}{
		// zero values (disabled)
		{
			settings.AuthProviderConfig{},
			false,
		},
		// zero values (enabled)
		{
			settings.AuthProviderConfig{Enabled: true},
			true,
		},
		// invalid data
		{
			settings.AuthProviderConfig{
				Enabled:      true,
				ClientId:     "",
				ClientSecret: "",
				AuthUrl:      "test",
				TokenUrl:     "test",
				UserApiUrl:   "test",
			},
			true,
		},
		// valid data (only the required)
		{
			settings.AuthProviderConfig{
				Enabled:      true,
				ClientId:     "test",
				ClientSecret: "test",
			},
			false,
		},
		// valid data (fill all fields)
		{
			settings.AuthProviderConfig{
				Enabled:      true,
				ClientId:     "test",
				ClientSecret: "test",
				AuthUrl:      "https://example.com",
				TokenUrl:     "https://example.com",
				UserApiUrl:   "https://example.com",
			},
			false,
		},
	}

	for i, scenario := range scenarios {
		result := scenario.config.Validate()

		if result != nil && !scenario.expectError {
			t.Errorf("(%d) Didn't expect error, got %v", i, result)
		}

		if result == nil && scenario.expectError {
			t.Errorf("(%d) Expected error, got nil", i)
		}
	}
}

func TestAuthProviderConfigSetupProvider(t *testing.T) {
	provider := auth.NewGithubProvider()

	// disabled config
	c1 := settings.AuthProviderConfig{Enabled: false}
	if err := c1.SetupProvider(provider); err == nil {
		t.Errorf("Expected error, got nil")
	}

	c2 := settings.AuthProviderConfig{
		Enabled:      true,
		ClientId:     "test_ClientId",
		ClientSecret: "test_ClientSecret",
		AuthUrl:      "test_AuthUrl",
		UserApiUrl:   "test_UserApiUrl",
		TokenUrl:     "test_TokenUrl",
	}
	if err := c2.SetupProvider(provider); err != nil {
		t.Error(err)
	}

	if provider.ClientId() != c2.ClientId {
		t.Fatalf("Expected ClientId %s, got %s", c2.ClientId, provider.ClientId())
	}

	if provider.ClientSecret() != c2.ClientSecret {
		t.Fatalf("Expected ClientSecret %s, got %s", c2.ClientSecret, provider.ClientSecret())
	}

	if provider.AuthUrl() != c2.AuthUrl {
		t.Fatalf("Expected AuthUrl %s, got %s", c2.AuthUrl, provider.AuthUrl())
	}

	if provider.UserApiUrl() != c2.UserApiUrl {
		t.Fatalf("Expected UserApiUrl %s, got %s", c2.UserApiUrl, provider.UserApiUrl())
	}

	if provider.TokenUrl() != c2.TokenUrl {
		t.Fatalf("Expected TokenUrl %s, got %s", c2.TokenUrl, provider.TokenUrl())
	}
}
