package core_test

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

	"github.com/pocketbase/pocketbase/core"
	"github.com/pocketbase/pocketbase/tools/auth"
)

func TestSettingsValidate(t *testing.T) {
	s := core.NewSettings()

	// 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.UserAuthToken.Duration = -10
	s.UserPasswordResetToken.Duration = -10
	s.UserEmailChangeToken.Duration = -10
	s.UserVerificationToken.Duration = -10
	s.EmailAuth.Enabled = true
	s.EmailAuth.MinPasswordLength = -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 = ""

	// 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":{`,
		`"userAuthToken":{`,
		`"userPasswordResetToken":{`,
		`"userEmailChangeToken":{`,
		`"userVerificationToken":{`,
		`"emailAuth":{`,
		`"googleAuth":{`,
		`"facebookAuth":{`,
		`"githubAuth":{`,
		`"gitlabAuth":{`,
	}

	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 := core.NewSettings()
	s1.Meta.AppUrl = "old_app_url"

	s2 := core.NewSettings()
	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.AdminAuthToken.Duration = 1
	s2.AdminPasswordResetToken.Duration = 2
	s2.UserAuthToken.Duration = 3
	s2.UserPasswordResetToken.Duration = 4
	s2.UserEmailChangeToken.Duration = 5
	s2.UserVerificationToken.Duration = 6
	s2.EmailAuth.Enabled = false
	s2.EmailAuth.MinPasswordLength = 30
	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"

	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 := core.NewSettings()

	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) {
	s1 := core.NewSettings()
	s1.Meta.AppName = "test123" // control field
	s1.Smtp.Password = "test123"
	s1.Smtp.Tls = true
	s1.S3.Secret = "test123"
	s1.AdminAuthToken.Secret = "test123"
	s1.AdminPasswordResetToken.Secret = "test123"
	s1.UserAuthToken.Secret = "test123"
	s1.UserPasswordResetToken.Secret = "test123"
	s1.UserEmailChangeToken.Secret = "test123"
	s1.UserVerificationToken.Secret = "test123"
	s1.GoogleAuth.ClientSecret = "test123"
	s1.FacebookAuth.ClientSecret = "test123"
	s1.GithubAuth.ClientSecret = "test123"
	s1.GitlabAuth.ClientSecret = "test123"

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

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

	expected := `{"meta":{"appName":"test123","appUrl":"http://localhost:8090","senderName":"Support","senderAddress":"support@example.com","userVerificationUrl":"%APP_URL%/_/#/users/confirm-verification/%TOKEN%","userResetPasswordUrl":"%APP_URL%/_/#/users/confirm-password-reset/%TOKEN%","userConfirmEmailChangeUrl":"%APP_URL%/_/#/users/confirm-email-change/%TOKEN%"},"logs":{"maxDays":7},"smtp":{"enabled":false,"host":"smtp.example.com","port":587,"username":"","password":"******","tls":true},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","secret":"******"},"adminAuthToken":{"secret":"******","duration":1209600},"adminPasswordResetToken":{"secret":"******","duration":1800},"userAuthToken":{"secret":"******","duration":1209600},"userPasswordResetToken":{"secret":"******","duration":1800},"userEmailChangeToken":{"secret":"******","duration":1800},"userVerificationToken":{"secret":"******","duration":604800},"emailAuth":{"enabled":true,"exceptDomains":null,"onlyDomains":null,"minPasswordLength":8},"googleAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"facebookAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"githubAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"gitlabAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"}}`

	if encodedStr := string(encoded); encodedStr != expected {
		t.Fatalf("Expected %v, got \n%v", expected, encodedStr)
	}
}

func TestNamedAuthProviderConfigs(t *testing.T) {
	s := core.NewSettings()

	s.GoogleAuth.ClientId = "google_test"
	s.FacebookAuth.ClientId = "facebook_test"
	s.GithubAuth.ClientId = "github_test"
	s.GitlabAuth.ClientId = "gitlab_test"
	s.GitlabAuth.Enabled = true

	result := s.NamedAuthProviderConfigs()

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

	expected := `{"facebook":{"enabled":false,"allowRegistrations":true,"clientId":"facebook_test"},"github":{"enabled":false,"allowRegistrations":true,"clientId":"github_test"},"gitlab":{"enabled":true,"allowRegistrations":true,"clientId":"gitlab_test"},"google":{"enabled":false,"allowRegistrations":true,"clientId":"google_test"}}`

	if encodedStr := string(encoded); encodedStr != expected {
		t.Fatalf("Expected the same serialization, got %v", encodedStr)
	}
}

func TestTokenConfigValidate(t *testing.T) {
	scenarios := []struct {
		config      core.TokenConfig
		expectError bool
	}{
		// zero values
		{
			core.TokenConfig{},
			true,
		},
		// invalid data
		{
			core.TokenConfig{
				Secret:   "test",
				Duration: 4,
			},
			true,
		},
		// valid data
		{
			core.TokenConfig{
				Secret:   "testtesttesttesttesttesttestte",
				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      core.SmtpConfig
		expectError bool
	}{
		// zero values (disabled)
		{
			core.SmtpConfig{},
			false,
		},
		// zero values (enabled)
		{
			core.SmtpConfig{Enabled: true},
			true,
		},
		// invalid data
		{
			core.SmtpConfig{
				Enabled: true,
				Host:    "test:test:test",
				Port:    -10,
			},
			true,
		},
		// valid data
		{
			core.SmtpConfig{
				Enabled: true,
				Host:    "example.com",
				Port:    100,
				Tls:     true,
			},
			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      core.S3Config
		expectError bool
	}{
		// zero values (disabled)
		{
			core.S3Config{},
			false,
		},
		// zero values (enabled)
		{
			core.S3Config{Enabled: true},
			true,
		},
		// invalid data
		{
			core.S3Config{
				Enabled:  true,
				Endpoint: "test:test:test",
			},
			true,
		},
		// valid data (url endpoint)
		{
			core.S3Config{
				Enabled:   true,
				Endpoint:  "https://localhost:8090",
				Bucket:    "test",
				Region:    "test",
				AccessKey: "test",
				Secret:    "test",
			},
			false,
		},
		// valid data (hostname endpoint)
		{
			core.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) {
	scenarios := []struct {
		config      core.MetaConfig
		expectError bool
	}{
		// zero values
		{
			core.MetaConfig{},
			true,
		},
		// invalid data
		{
			core.MetaConfig{
				AppName:                   strings.Repeat("a", 300),
				AppUrl:                    "test",
				SenderName:                strings.Repeat("a", 300),
				SenderAddress:             "invalid_email",
				UserVerificationUrl:       "test",
				UserResetPasswordUrl:      "test",
				UserConfirmEmailChangeUrl: "test",
			},
			true,
		},
		// invalid data (missing required placeholders)
		{
			core.MetaConfig{
				AppName:                   "test",
				AppUrl:                    "https://example.com",
				SenderName:                "test",
				SenderAddress:             "test@example.com",
				UserVerificationUrl:       "https://example.com",
				UserResetPasswordUrl:      "https://example.com",
				UserConfirmEmailChangeUrl: "https://example.com",
			},
			true,
		},
		// valid data
		{
			core.MetaConfig{
				AppName:                   "test",
				AppUrl:                    "https://example.com",
				SenderName:                "test",
				SenderAddress:             "test@example.com",
				UserVerificationUrl:       "https://example.com/" + core.EmailPlaceholderToken,
				UserResetPasswordUrl:      "https://example.com/" + core.EmailPlaceholderToken,
				UserConfirmEmailChangeUrl: "https://example.com/" + core.EmailPlaceholderToken,
			},
			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 TestLogsConfigValidate(t *testing.T) {
	scenarios := []struct {
		config      core.LogsConfig
		expectError bool
	}{
		// zero values
		{
			core.LogsConfig{},
			false,
		},
		// invalid data
		{
			core.LogsConfig{MaxDays: -10},
			true,
		},
		// valid data
		{
			core.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      core.AuthProviderConfig
		expectError bool
	}{
		// zero values (disabled)
		{
			core.AuthProviderConfig{},
			false,
		},
		// zero values (enabled)
		{
			core.AuthProviderConfig{Enabled: true},
			true,
		},
		// invalid data
		{
			core.AuthProviderConfig{
				Enabled:      true,
				ClientId:     "",
				ClientSecret: "",
				AuthUrl:      "test",
				TokenUrl:     "test",
				UserApiUrl:   "test",
			},
			true,
		},
		// valid data (only the required)
		{
			core.AuthProviderConfig{
				Enabled:      true,
				ClientId:     "test",
				ClientSecret: "test",
			},
			false,
		},
		// valid data (fill all fields)
		{
			core.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 := core.AuthProviderConfig{Enabled: false}
	if err := c1.SetupProvider(provider); err == nil {
		t.Errorf("Expected error, got nil")
	}

	c2 := core.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)
	}
	encoded, _ := json.Marshal(c2)
	expected := `{"enabled":true,"allowRegistrations":false,"clientId":"test_ClientId","clientSecret":"test_ClientSecret","authUrl":"test_AuthUrl","tokenUrl":"test_TokenUrl","userApiUrl":"test_UserApiUrl"}`
	if string(encoded) != expected {
		t.Errorf("Expected %s, got %s", expected, string(encoded))
	}
}

func TestEmailAuthConfigValidate(t *testing.T) {
	scenarios := []struct {
		config      core.EmailAuthConfig
		expectError bool
	}{
		// zero values (disabled)
		{
			core.EmailAuthConfig{},
			false,
		},
		// zero values (enabled)
		{
			core.EmailAuthConfig{Enabled: true},
			true,
		},
		// invalid data (only the required)
		{
			core.EmailAuthConfig{
				Enabled:           true,
				MinPasswordLength: 4,
			},
			true,
		},
		// valid data (only the required)
		{
			core.EmailAuthConfig{
				Enabled:           true,
				MinPasswordLength: 5,
			},
			false,
		},
		// invalid data (both OnlyDomains and ExceptDomains set)
		{
			core.EmailAuthConfig{
				Enabled:           true,
				MinPasswordLength: 5,
				OnlyDomains:       []string{"example.com", "test.com"},
				ExceptDomains:     []string{"example.com", "test.com"},
			},
			true,
		},
		// valid data (only onlyDomains set)
		{
			core.EmailAuthConfig{
				Enabled:           true,
				MinPasswordLength: 5,
				OnlyDomains:       []string{"example.com", "test.com"},
			},
			false,
		},
		// valid data (only exceptDomains set)
		{
			core.EmailAuthConfig{
				Enabled:           true,
				MinPasswordLength: 5,
				ExceptDomains:     []string{"example.com", "test.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)
		}
	}
}
