package core_test

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

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

func TestCollectionAuthOptionsValidate(t *testing.T) {
	t.Parallel()

	scenarios := []struct {
		name           string
		collection     func(app core.App) (*core.Collection, error)
		expectedErrors []string
	}{
		// authRule
		{
			name: "nil authRule",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.AuthRule = nil
				return c, nil
			},
			expectedErrors: []string{},
		},
		{
			name: "empty authRule",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.AuthRule = types.Pointer("")
				return c, nil
			},
			expectedErrors: []string{},
		},
		{
			name: "invalid authRule",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.AuthRule = types.Pointer("missing != ''")
				return c, nil
			},
			expectedErrors: []string{"authRule"},
		},
		{
			name: "valid authRule",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.AuthRule = types.Pointer("id != ''")
				return c, nil
			},
			expectedErrors: []string{},
		},

		// manageRule
		{
			name: "nil manageRule",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.ManageRule = nil
				return c, nil
			},
			expectedErrors: []string{},
		},
		{
			name: "empty manageRule",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.ManageRule = types.Pointer("")
				return c, nil
			},
			expectedErrors: []string{"manageRule"},
		},
		{
			name: "invalid manageRule",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.ManageRule = types.Pointer("missing != ''")
				return c, nil
			},
			expectedErrors: []string{"manageRule"},
		},
		{
			name: "valid manageRule",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.ManageRule = types.Pointer("id != ''")
				return c, nil
			},
			expectedErrors: []string{},
		},

		// passwordAuth
		{
			name: "trigger passwordAuth validations",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.PasswordAuth = core.PasswordAuthConfig{
					Enabled: true,
				}
				return c, nil
			},
			expectedErrors: []string{"passwordAuth"},
		},
		{
			name: "passwordAuth with non-unique identity fields",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.Fields.Add(&core.TextField{Name: "test"})
				c.PasswordAuth = core.PasswordAuthConfig{
					Enabled:        true,
					IdentityFields: []string{"email", "test"},
				}
				return c, nil
			},
			expectedErrors: []string{"passwordAuth"},
		},
		{
			name: "passwordAuth with non-unique identity fields",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.Fields.Add(&core.TextField{Name: "test"})
				c.AddIndex("auth_test_idx", true, "test", "")
				c.PasswordAuth = core.PasswordAuthConfig{
					Enabled:        true,
					IdentityFields: []string{"email", "test"},
				}
				return c, nil
			},
			expectedErrors: []string{},
		},

		// oauth2
		{
			name: "trigger oauth2 validations",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.OAuth2 = core.OAuth2Config{
					Enabled: true,
					Providers: []core.OAuth2ProviderConfig{
						{Name: "missing"},
					},
				}
				return c, nil
			},
			expectedErrors: []string{"oauth2"},
		},

		// otp
		{
			name: "trigger otp validations",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.OTP = core.OTPConfig{
					Enabled:  true,
					Duration: -10,
				}
				return c, nil
			},
			expectedErrors: []string{"otp"},
		},

		// mfa
		{
			name: "trigger mfa validations",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.MFA = core.MFAConfig{
					Enabled:  true,
					Duration: -10,
				}
				return c, nil
			},
			expectedErrors: []string{"mfa"},
		},
		{
			name: "mfa enabled with < 2 auth methods",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.MFA.Enabled = true
				c.PasswordAuth.Enabled = true
				c.OTP.Enabled = false
				c.OAuth2.Enabled = false
				return c, nil
			},
			expectedErrors: []string{"mfa"},
		},
		{
			name: "mfa enabled with >= 2 auth methods",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.MFA.Enabled = true
				c.PasswordAuth.Enabled = true
				c.OTP.Enabled = true
				c.OAuth2.Enabled = false
				return c, nil
			},
			expectedErrors: []string{},
		},
		{
			name: "mfa disabled with invalid rule",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.PasswordAuth.Enabled = true
				c.OTP.Enabled = true
				c.MFA.Enabled = false
				c.MFA.Rule = "invalid"
				return c, nil
			},
			expectedErrors: []string{},
		},
		{
			name: "mfa enabled with invalid rule",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.PasswordAuth.Enabled = true
				c.OTP.Enabled = true
				c.MFA.Enabled = true
				c.MFA.Rule = "invalid"
				return c, nil
			},
			expectedErrors: []string{"mfa"},
		},
		{
			name: "mfa enabled with valid rule",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.PasswordAuth.Enabled = true
				c.OTP.Enabled = true
				c.MFA.Enabled = true
				c.MFA.Rule = "1=1"
				return c, nil
			},
			expectedErrors: []string{},
		},

		// tokens
		{
			name: "trigger authToken validations",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.AuthToken.Secret = ""
				return c, nil
			},
			expectedErrors: []string{"authToken"},
		},
		{
			name: "trigger passwordResetToken validations",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.PasswordResetToken.Secret = ""
				return c, nil
			},
			expectedErrors: []string{"passwordResetToken"},
		},
		{
			name: "trigger emailChangeToken validations",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.EmailChangeToken.Secret = ""
				return c, nil
			},
			expectedErrors: []string{"emailChangeToken"},
		},
		{
			name: "trigger verificationToken validations",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.VerificationToken.Secret = ""
				return c, nil
			},
			expectedErrors: []string{"verificationToken"},
		},
		{
			name: "trigger fileToken validations",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.FileToken.Secret = ""
				return c, nil
			},
			expectedErrors: []string{"fileToken"},
		},

		// templates
		{
			name: "trigger verificationTemplate validations",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.VerificationTemplate.Body = ""
				return c, nil
			},
			expectedErrors: []string{"verificationTemplate"},
		},
		{
			name: "trigger resetPasswordTemplate validations",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.ResetPasswordTemplate.Body = ""
				return c, nil
			},
			expectedErrors: []string{"resetPasswordTemplate"},
		},
		{
			name: "trigger confirmEmailChangeTemplate validations",
			collection: func(app core.App) (*core.Collection, error) {
				c := core.NewAuthCollection("new_auth")
				c.ConfirmEmailChangeTemplate.Body = ""
				return c, nil
			},
			expectedErrors: []string{"confirmEmailChangeTemplate"},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			app, _ := tests.NewTestApp()
			defer app.Cleanup()

			collection, err := s.collection(app)
			if err != nil {
				t.Fatalf("Failed to retrieve test collection: %v", err)
			}

			result := app.Validate(collection)

			tests.TestValidationErrors(t, result, s.expectedErrors)
		})
	}
}

func TestEmailTemplateValidate(t *testing.T) {
	scenarios := []struct {
		name           string
		template       core.EmailTemplate
		expectedErrors []string
	}{
		{
			"zero value",
			core.EmailTemplate{},
			[]string{"subject", "body"},
		},
		{
			"non-empty data",
			core.EmailTemplate{
				Subject: "a",
				Body:    "b",
			},
			[]string{},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			result := s.template.Validate()

			tests.TestValidationErrors(t, result, s.expectedErrors)
		})
	}
}

func TestEmailTemplateResolve(t *testing.T) {
	template := core.EmailTemplate{
		Subject: "test_subject {PARAM3} {PARAM1}-{PARAM2} repeat-{PARAM1}",
		Body:    "test_body {PARAM3} {PARAM2}-{PARAM1} repeat-{PARAM2}",
	}

	scenarios := []struct {
		name            string
		placeholders    map[string]any
		template        core.EmailTemplate
		expectedSubject string
		expectedBody    string
	}{
		{
			"no placeholders",
			nil,
			template,
			template.Subject,
			template.Body,
		},
		{
			"no matching placeholders",
			map[string]any{"{A}": "abc", "{B}": 456},
			template,
			template.Subject,
			template.Body,
		},
		{
			"at least one matching placeholder",
			map[string]any{"{PARAM1}": "abc", "{PARAM2}": 456},
			template,
			"test_subject {PARAM3} abc-456 repeat-abc",
			"test_body {PARAM3} 456-abc repeat-456",
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			subject, body := s.template.Resolve(s.placeholders)

			if subject != s.expectedSubject {
				t.Fatalf("Expected subject\n%v\ngot\n%v", s.expectedSubject, subject)
			}

			if body != s.expectedBody {
				t.Fatalf("Expected body\n%v\ngot\n%v", s.expectedBody, body)
			}
		})
	}
}

func TestTokenConfigValidate(t *testing.T) {
	scenarios := []struct {
		name           string
		config         core.TokenConfig
		expectedErrors []string
	}{
		{
			"zero value",
			core.TokenConfig{},
			[]string{"secret", "duration"},
		},
		{
			"invalid data",
			core.TokenConfig{
				Secret:   strings.Repeat("a", 29),
				Duration: 9,
			},
			[]string{"secret", "duration"},
		},
		{
			"valid data",
			core.TokenConfig{
				Secret:   strings.Repeat("a", 30),
				Duration: 10,
			},
			[]string{},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			result := s.config.Validate()

			tests.TestValidationErrors(t, result, s.expectedErrors)
		})
	}
}

func TestTokenConfigDurationTime(t *testing.T) {
	scenarios := []struct {
		config   core.TokenConfig
		expected time.Duration
	}{
		{core.TokenConfig{}, 0 * time.Second},
		{core.TokenConfig{Duration: 1234}, 1234 * time.Second},
	}

	for i, s := range scenarios {
		t.Run(fmt.Sprintf("%d_%d", i, s.config.Duration), func(t *testing.T) {
			result := s.config.DurationTime()

			if result != s.expected {
				t.Fatalf("Expected duration %d, got %d", s.expected, result)
			}
		})
	}
}

func TestAuthAlertConfigValidate(t *testing.T) {
	scenarios := []struct {
		name           string
		config         core.AuthAlertConfig
		expectedErrors []string
	}{
		{
			"zero value (disabled)",
			core.AuthAlertConfig{},
			[]string{"emailTemplate"},
		},
		{
			"zero value (enabled)",
			core.AuthAlertConfig{Enabled: true},
			[]string{"emailTemplate"},
		},
		{
			"invalid template",
			core.AuthAlertConfig{
				EmailTemplate: core.EmailTemplate{Body: "", Subject: "b"},
			},
			[]string{"emailTemplate"},
		},
		{
			"valid data",
			core.AuthAlertConfig{
				EmailTemplate: core.EmailTemplate{Body: "a", Subject: "b"},
			},
			[]string{},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			result := s.config.Validate()

			tests.TestValidationErrors(t, result, s.expectedErrors)
		})
	}
}

func TestOTPConfigValidate(t *testing.T) {
	scenarios := []struct {
		name           string
		config         core.OTPConfig
		expectedErrors []string
	}{
		{
			"zero value (disabled)",
			core.OTPConfig{},
			[]string{"emailTemplate"},
		},
		{
			"zero value (enabled)",
			core.OTPConfig{Enabled: true},
			[]string{"duration", "length", "emailTemplate"},
		},
		{
			"invalid length (< 3)",
			core.OTPConfig{
				Enabled:       true,
				EmailTemplate: core.EmailTemplate{Body: "a", Subject: "b"},
				Duration:      100,
				Length:        3,
			},
			[]string{"length"},
		},
		{
			"invalid duration (< 10)",
			core.OTPConfig{
				Enabled:       true,
				EmailTemplate: core.EmailTemplate{Body: "a", Subject: "b"},
				Duration:      9,
				Length:        100,
			},
			[]string{"duration"},
		},
		{
			"invalid duration (> 86400)",
			core.OTPConfig{
				Enabled:       true,
				EmailTemplate: core.EmailTemplate{Body: "a", Subject: "b"},
				Duration:      86401,
				Length:        100,
			},
			[]string{"duration"},
		},
		{
			"invalid template (triggering EmailTemplate validations)",
			core.OTPConfig{
				Enabled:       true,
				EmailTemplate: core.EmailTemplate{Body: "", Subject: "b"},
				Duration:      86400,
				Length:        4,
			},
			[]string{"emailTemplate"},
		},
		{
			"valid data",
			core.OTPConfig{
				Enabled:       true,
				EmailTemplate: core.EmailTemplate{Body: "a", Subject: "b"},
				Duration:      86400,
				Length:        4,
			},
			[]string{},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			result := s.config.Validate()

			tests.TestValidationErrors(t, result, s.expectedErrors)
		})
	}
}

func TestOTPConfigDurationTime(t *testing.T) {
	scenarios := []struct {
		config   core.OTPConfig
		expected time.Duration
	}{
		{core.OTPConfig{}, 0 * time.Second},
		{core.OTPConfig{Duration: 1234}, 1234 * time.Second},
	}

	for i, s := range scenarios {
		t.Run(fmt.Sprintf("%d_%d", i, s.config.Duration), func(t *testing.T) {
			result := s.config.DurationTime()

			if result != s.expected {
				t.Fatalf("Expected duration %d, got %d", s.expected, result)
			}
		})
	}
}

func TestMFAConfigValidate(t *testing.T) {
	scenarios := []struct {
		name           string
		config         core.MFAConfig
		expectedErrors []string
	}{
		{
			"zero value (disabled)",
			core.MFAConfig{},
			[]string{},
		},
		{
			"zero value (enabled)",
			core.MFAConfig{Enabled: true},
			[]string{"duration"},
		},
		{
			"invalid duration (< 10)",
			core.MFAConfig{Enabled: true, Duration: 9},
			[]string{"duration"},
		},
		{
			"invalid duration (> 86400)",
			core.MFAConfig{Enabled: true, Duration: 86401},
			[]string{"duration"},
		},
		{
			"valid data",
			core.MFAConfig{Enabled: true, Duration: 86400},
			[]string{},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			result := s.config.Validate()

			tests.TestValidationErrors(t, result, s.expectedErrors)
		})
	}
}

func TestMFAConfigDurationTime(t *testing.T) {
	scenarios := []struct {
		config   core.MFAConfig
		expected time.Duration
	}{
		{core.MFAConfig{}, 0 * time.Second},
		{core.MFAConfig{Duration: 1234}, 1234 * time.Second},
	}

	for i, s := range scenarios {
		t.Run(fmt.Sprintf("%d_%d", i, s.config.Duration), func(t *testing.T) {
			result := s.config.DurationTime()

			if result != s.expected {
				t.Fatalf("Expected duration %d, got %d", s.expected, result)
			}
		})
	}
}

func TestPasswordAuthConfigValidate(t *testing.T) {
	scenarios := []struct {
		name           string
		config         core.PasswordAuthConfig
		expectedErrors []string
	}{
		{
			"zero value (disabled)",
			core.PasswordAuthConfig{},
			[]string{},
		},
		{
			"zero value (enabled)",
			core.PasswordAuthConfig{Enabled: true},
			[]string{"identityFields"},
		},
		{
			"empty values",
			core.PasswordAuthConfig{Enabled: true, IdentityFields: []string{"", ""}},
			[]string{"identityFields"},
		},
		{
			"valid data",
			core.PasswordAuthConfig{Enabled: true, IdentityFields: []string{"abc"}},
			[]string{},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			result := s.config.Validate()

			tests.TestValidationErrors(t, result, s.expectedErrors)
		})
	}
}

func TestOAuth2ConfigGetProviderConfig(t *testing.T) {
	scenarios := []struct {
		name           string
		providerName   string
		config         core.OAuth2Config
		expectedExists bool
	}{
		{
			"zero value",
			"gitlab",
			core.OAuth2Config{},
			false,
		},
		{
			"empty config with valid provider",
			"gitlab",
			core.OAuth2Config{},
			false,
		},
		{
			"non-empty config with missing provider",
			"gitlab",
			core.OAuth2Config{Providers: []core.OAuth2ProviderConfig{{Name: "google"}, {Name: "github"}}},
			false,
		},
		{
			"config with existing provider",
			"github",
			core.OAuth2Config{Providers: []core.OAuth2ProviderConfig{{Name: "google"}, {Name: "github"}}},
			true,
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			config, exists := s.config.GetProviderConfig(s.providerName)

			if exists != s.expectedExists {
				t.Fatalf("Expected exists %v, got %v", s.expectedExists, exists)
			}

			if exists {
				if config.Name != s.providerName {
					t.Fatalf("Expected config with name %q, got %q", s.providerName, config.Name)
				}
			} else {
				if config.Name != "" {
					t.Fatalf("Expected empty config, got %v", config)
				}
			}
		})
	}
}

func TestOAuth2ConfigValidate(t *testing.T) {
	scenarios := []struct {
		name           string
		config         core.OAuth2Config
		expectedErrors []string
	}{
		{
			"zero value (disabled)",
			core.OAuth2Config{},
			[]string{},
		},
		{
			"zero value (enabled)",
			core.OAuth2Config{Enabled: true},
			[]string{},
		},
		{
			"unknown provider",
			core.OAuth2Config{Enabled: true, Providers: []core.OAuth2ProviderConfig{
				{Name: "missing", ClientId: "abc", ClientSecret: "456"},
			}},
			[]string{"providers"},
		},
		{
			"known provider with invalid data",
			core.OAuth2Config{Enabled: true, Providers: []core.OAuth2ProviderConfig{
				{Name: "gitlab", ClientId: "abc", TokenURL: "!invalid!"},
			}},
			[]string{"providers"},
		},
		{
			"known provider with valid data",
			core.OAuth2Config{Enabled: true, Providers: []core.OAuth2ProviderConfig{
				{Name: "gitlab", ClientId: "abc", ClientSecret: "456", TokenURL: "https://example.com"},
			}},
			[]string{},
		},
		{
			"known provider with valid data (duplicated)",
			core.OAuth2Config{Enabled: true, Providers: []core.OAuth2ProviderConfig{
				{Name: "gitlab", ClientId: "abc1", ClientSecret: "1", TokenURL: "https://example1.com"},
				{Name: "google", ClientId: "abc2", ClientSecret: "2", TokenURL: "https://example2.com"},
				{Name: "gitlab", ClientId: "abc3", ClientSecret: "3", TokenURL: "https://example3.com"},
			}},
			[]string{"providers"},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			result := s.config.Validate()

			tests.TestValidationErrors(t, result, s.expectedErrors)
		})
	}
}

func TestOAuth2ProviderConfigValidate(t *testing.T) {
	scenarios := []struct {
		name           string
		config         core.OAuth2ProviderConfig
		expectedErrors []string
	}{
		{
			"zero value",
			core.OAuth2ProviderConfig{},
			[]string{"name", "clientId", "clientSecret"},
		},
		{
			"minimum valid data",
			core.OAuth2ProviderConfig{Name: "gitlab", ClientId: "abc", ClientSecret: "456"},
			[]string{},
		},
		{
			"non-existing provider",
			core.OAuth2ProviderConfig{Name: "missing", ClientId: "abc", ClientSecret: "456"},
			[]string{"name"},
		},
		{
			"invalid urls",
			core.OAuth2ProviderConfig{
				Name:         "gitlab",
				ClientId:     "abc",
				ClientSecret: "456",
				AuthURL:      "!invalid!",
				TokenURL:     "!invalid!",
				UserInfoURL:  "!invalid!",
			},
			[]string{"authURL", "tokenURL", "userInfoURL"},
		},
		{
			"valid urls",
			core.OAuth2ProviderConfig{
				Name:         "gitlab",
				ClientId:     "abc",
				ClientSecret: "456",
				AuthURL:      "https://example.com/a",
				TokenURL:     "https://example.com/b",
				UserInfoURL:  "https://example.com/c",
			},
			[]string{},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			result := s.config.Validate()

			tests.TestValidationErrors(t, result, s.expectedErrors)
		})
	}
}

func TestOAuth2ProviderConfigInitProvider(t *testing.T) {
	scenarios := []struct {
		name           string
		config         core.OAuth2ProviderConfig
		expectedConfig core.OAuth2ProviderConfig
		expectedError  bool
	}{
		{
			"empty config",
			core.OAuth2ProviderConfig{},
			core.OAuth2ProviderConfig{},
			true,
		},
		{
			"missing provider",
			core.OAuth2ProviderConfig{
				Name:         "missing",
				ClientId:     "test_ClientId",
				ClientSecret: "test_ClientSecret",
				AuthURL:      "test_AuthURL",
				TokenURL:     "test_TokenURL",
				UserInfoURL:  "test_UserInfoURL",
				DisplayName:  "test_DisplayName",
				PKCE:         types.Pointer(true),
			},
			core.OAuth2ProviderConfig{
				Name:         "missing",
				ClientId:     "test_ClientId",
				ClientSecret: "test_ClientSecret",
				AuthURL:      "test_AuthURL",
				TokenURL:     "test_TokenURL",
				UserInfoURL:  "test_UserInfoURL",
				DisplayName:  "test_DisplayName",
				PKCE:         types.Pointer(true),
			},
			true,
		},
		{
			"existing provider minimal",
			core.OAuth2ProviderConfig{
				Name: "gitlab",
			},
			core.OAuth2ProviderConfig{
				Name:         "gitlab",
				ClientId:     "",
				ClientSecret: "",
				AuthURL:      "https://gitlab.com/oauth/authorize",
				TokenURL:     "https://gitlab.com/oauth/token",
				UserInfoURL:  "https://gitlab.com/api/v4/user",
				DisplayName:  "GitLab",
				PKCE:         types.Pointer(true),
			},
			false,
		},
		{
			"existing provider with all fields",
			core.OAuth2ProviderConfig{
				Name:         "gitlab",
				ClientId:     "test_ClientId",
				ClientSecret: "test_ClientSecret",
				AuthURL:      "test_AuthURL",
				TokenURL:     "test_TokenURL",
				UserInfoURL:  "test_UserInfoURL",
				DisplayName:  "test_DisplayName",
				PKCE:         types.Pointer(true),
				Extra:        map[string]any{"a": 1},
			},
			core.OAuth2ProviderConfig{
				Name:         "gitlab",
				ClientId:     "test_ClientId",
				ClientSecret: "test_ClientSecret",
				AuthURL:      "test_AuthURL",
				TokenURL:     "test_TokenURL",
				UserInfoURL:  "test_UserInfoURL",
				DisplayName:  "test_DisplayName",
				PKCE:         types.Pointer(true),
				Extra:        map[string]any{"a": 1},
			},
			false,
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			provider, err := s.config.InitProvider()

			hasErr := err != nil
			if hasErr != s.expectedError {
				t.Fatalf("Expected hasErr %v, got %v", s.expectedError, hasErr)
			}

			if hasErr {
				if provider != nil {
					t.Fatalf("Expected nil provider, got %v", provider)
				}
				return
			}

			factory, ok := auth.Providers[s.expectedConfig.Name]
			if !ok {
				t.Fatalf("Missing factory for provider %q", s.expectedConfig.Name)
			}

			expectedType := fmt.Sprintf("%T", factory())
			providerType := fmt.Sprintf("%T", provider)
			if expectedType != providerType {
				t.Fatalf("Expected provider instanceof %q, got %q", expectedType, providerType)
			}

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

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

			if provider.AuthURL() != s.expectedConfig.AuthURL {
				t.Fatalf("Expected AuthURL %q, got %q", s.expectedConfig.AuthURL, provider.AuthURL())
			}

			if provider.UserInfoURL() != s.expectedConfig.UserInfoURL {
				t.Fatalf("Expected UserInfoURL %q, got %q", s.expectedConfig.UserInfoURL, provider.UserInfoURL())
			}

			if provider.TokenURL() != s.expectedConfig.TokenURL {
				t.Fatalf("Expected TokenURL %q, got %q", s.expectedConfig.TokenURL, provider.TokenURL())
			}

			if provider.DisplayName() != s.expectedConfig.DisplayName {
				t.Fatalf("Expected DisplayName %q, got %q", s.expectedConfig.DisplayName, provider.DisplayName())
			}

			if provider.PKCE() != *s.expectedConfig.PKCE {
				t.Fatalf("Expected PKCE %v, got %v", *s.expectedConfig.PKCE, provider.PKCE())
			}

			rawMeta, _ := json.Marshal(provider.Extra())
			expectedMeta, _ := json.Marshal(s.expectedConfig.Extra)
			if !bytes.Equal(rawMeta, expectedMeta) {
				t.Fatalf("Expected PKCE %v, got %v", *s.expectedConfig.PKCE, provider.PKCE())
			}
		})
	}
}