package config

import (
	"reflect"
	"testing"
)

func TestNormaliseList(t *testing.T) {
	tests := []struct {
		name   string
		values []string
		want   []string
	}{
		{
			name:   "empty slice",
			values: []string{},
			want:   []string{},
		},
		{
			name:   "nil slice",
			values: nil,
			want:   []string{},
		},
		{
			name:   "single value",
			values: []string{"foo"},
			want:   []string{"foo"},
		},
		{
			name:   "trims whitespace",
			values: []string{"  foo  ", "  bar  "},
			want:   []string{"foo", "bar"},
		},
		{
			name:   "removes empty strings",
			values: []string{"foo", "", "bar", "   "},
			want:   []string{"foo", "bar"},
		},
		{
			name:   "removes duplicates case insensitive",
			values: []string{"foo", "Foo", "FOO", "bar"},
			want:   []string{"foo", "bar"},
		},
		{
			name:   "preserves first occurrence",
			values: []string{"Foo", "foo", "bar", "Bar"},
			want:   []string{"Foo", "bar"},
		},
		{
			name:   "complex example",
			values: []string{"  openid  ", "profile", "", "  email  ", "OPENID", "Profile"},
			want:   []string{"openid", "profile", "email"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := normaliseList(tt.values)
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("normaliseList(%v) = %v, want %v", tt.values, got, tt.want)
			}
		})
	}
}

func TestParseDelimited(t *testing.T) {
	tests := []struct {
		name  string
		input string
		want  []string
	}{
		{
			name:  "empty string",
			input: "",
			want:  nil,
		},
		{
			name:  "whitespace only",
			input: "   ",
			want:  nil,
		},
		{
			name:  "comma separated",
			input: "foo,bar,baz",
			want:  []string{"foo", "bar", "baz"},
		},
		{
			name:  "space separated",
			input: "foo bar baz",
			want:  []string{"foo", "bar", "baz"},
		},
		{
			name:  "mixed separators",
			input: "foo, bar  baz,qux",
			want:  []string{"foo", "bar", "baz", "qux"},
		},
		{
			name:  "removes duplicates",
			input: "foo,bar,foo,BAR",
			want:  []string{"foo", "bar"},
		},
		{
			name:  "trims surrounding whitespace",
			input: "  foo , bar  ",
			want:  []string{"foo", "bar"},
		},
		{
			name:  "single value",
			input: "foo",
			want:  []string{"foo"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := parseDelimited(tt.input)
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("parseDelimited(%q) = %v, want %v", tt.input, got, tt.want)
			}
		})
	}
}

func TestDefaultRedirectURL(t *testing.T) {
	tests := []struct {
		name      string
		publicURL string
		want      string
	}{
		{
			name:      "empty string",
			publicURL: "",
			want:      "",
		},
		{
			name:      "whitespace only",
			publicURL: "   ",
			want:      "",
		},
		{
			name:      "simple URL",
			publicURL: "https://pulse.example.com",
			want:      "https://pulse.example.com/api/oidc/callback",
		},
		{
			name:      "URL with trailing slash",
			publicURL: "https://pulse.example.com/",
			want:      "https://pulse.example.com/api/oidc/callback",
		},
		{
			name:      "URL with path",
			publicURL: "https://example.com/pulse",
			want:      "https://example.com/pulse/api/oidc/callback",
		},
		{
			name:      "URL with path and trailing slash",
			publicURL: "https://example.com/pulse/",
			want:      "https://example.com/pulse/api/oidc/callback",
		},
		{
			name:      "URL with port",
			publicURL: "https://pulse.example.com:8443",
			want:      "https://pulse.example.com:8443/api/oidc/callback",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := DefaultRedirectURL(tt.publicURL)
			if got != tt.want {
				t.Errorf("DefaultRedirectURL(%q) = %q, want %q", tt.publicURL, got, tt.want)
			}
		})
	}
}

func TestOIDCConfigClone(t *testing.T) {
	t.Run("nil config", func(t *testing.T) {
		var cfg *OIDCConfig
		clone := cfg.Clone()
		if clone != nil {
			t.Error("Clone() of nil should return nil")
		}
	})

	t.Run("deep copy", func(t *testing.T) {
		cfg := &OIDCConfig{
			Enabled:        true,
			IssuerURL:      "https://issuer.example.com",
			ClientID:       "client-123",
			ClientSecret:   "secret",
			RedirectURL:    "https://pulse.example.com/callback",
			Scopes:         []string{"openid", "profile"},
			AllowedGroups:  []string{"admin", "users"},
			AllowedDomains: []string{"example.com"},
			AllowedEmails:  []string{"user@example.com"},
			EnvOverrides:   map[string]bool{"enabled": true},
		}

		clone := cfg.Clone()

		// Should be equal
		if clone.Enabled != cfg.Enabled {
			t.Error("Enabled should be equal")
		}
		if clone.IssuerURL != cfg.IssuerURL {
			t.Error("IssuerURL should be equal")
		}

		// Modify clone slices - originals should be unchanged
		clone.Scopes[0] = "modified"
		if cfg.Scopes[0] == "modified" {
			t.Error("Clone should have independent Scopes slice")
		}

		clone.AllowedGroups[0] = "modified"
		if cfg.AllowedGroups[0] == "modified" {
			t.Error("Clone should have independent AllowedGroups slice")
		}

		clone.AllowedDomains[0] = "modified.com"
		if cfg.AllowedDomains[0] == "modified.com" {
			t.Error("Clone should have independent AllowedDomains slice")
		}

		clone.AllowedEmails[0] = "modified@example.com"
		if cfg.AllowedEmails[0] == "modified@example.com" {
			t.Error("Clone should have independent AllowedEmails slice")
		}

		// Modify clone EnvOverrides - originals should be unchanged
		clone.EnvOverrides["issuerUrl"] = true
		if cfg.EnvOverrides["issuerUrl"] {
			t.Error("Clone should have independent EnvOverrides map")
		}
	})
}

func TestOIDCConfigApplyDefaults(t *testing.T) {
	t.Run("nil config", func(t *testing.T) {
		var cfg *OIDCConfig
		cfg.ApplyDefaults("") // Should not panic
	})

	t.Run("default scopes", func(t *testing.T) {
		cfg := &OIDCConfig{}
		cfg.ApplyDefaults("")

		expectedScopes := []string{"openid", "profile", "email"}
		if !reflect.DeepEqual(cfg.Scopes, expectedScopes) {
			t.Errorf("Scopes = %v, want %v", cfg.Scopes, expectedScopes)
		}
	})

	t.Run("preserves custom scopes", func(t *testing.T) {
		cfg := &OIDCConfig{
			Scopes: []string{"openid", "custom"},
		}
		cfg.ApplyDefaults("")

		if len(cfg.Scopes) != 2 || cfg.Scopes[1] != "custom" {
			t.Errorf("Scopes = %v, want [openid custom]", cfg.Scopes)
		}
	})

	t.Run("default claims", func(t *testing.T) {
		cfg := &OIDCConfig{}
		cfg.ApplyDefaults("")

		if cfg.UsernameClaim != "preferred_username" {
			t.Errorf("UsernameClaim = %q, want %q", cfg.UsernameClaim, "preferred_username")
		}
		if cfg.EmailClaim != "email" {
			t.Errorf("EmailClaim = %q, want %q", cfg.EmailClaim, "email")
		}
	})

	t.Run("preserves custom claims", func(t *testing.T) {
		cfg := &OIDCConfig{
			UsernameClaim: "sub",
			EmailClaim:    "mail",
		}
		cfg.ApplyDefaults("")

		if cfg.UsernameClaim != "sub" {
			t.Errorf("UsernameClaim = %q, want %q", cfg.UsernameClaim, "sub")
		}
		if cfg.EmailClaim != "mail" {
			t.Errorf("EmailClaim = %q, want %q", cfg.EmailClaim, "mail")
		}
	})

	t.Run("sets redirect URL from public URL", func(t *testing.T) {
		cfg := &OIDCConfig{}
		cfg.ApplyDefaults("https://pulse.example.com")

		expected := "https://pulse.example.com/api/oidc/callback"
		if cfg.RedirectURL != expected {
			t.Errorf("RedirectURL = %q, want %q", cfg.RedirectURL, expected)
		}
	})

	t.Run("preserves explicit redirect URL", func(t *testing.T) {
		cfg := &OIDCConfig{
			RedirectURL: "https://custom.example.com/callback",
		}
		cfg.ApplyDefaults("https://pulse.example.com")

		if cfg.RedirectURL != "https://custom.example.com/callback" {
			t.Errorf("RedirectURL should not be overwritten")
		}
	})

	t.Run("normalises lists", func(t *testing.T) {
		cfg := &OIDCConfig{
			AllowedGroups:  []string{"admin", "  admin  ", "users"},
			AllowedDomains: []string{"  example.com  "},
			AllowedEmails:  []string{"user@example.com", ""},
		}
		cfg.ApplyDefaults("")

		if len(cfg.AllowedGroups) != 2 {
			t.Errorf("AllowedGroups should be deduplicated, got %v", cfg.AllowedGroups)
		}
		if cfg.AllowedDomains[0] != "example.com" {
			t.Errorf("AllowedDomains should be trimmed, got %v", cfg.AllowedDomains)
		}
		if len(cfg.AllowedEmails) != 1 {
			t.Errorf("AllowedEmails should have empty entries removed, got %v", cfg.AllowedEmails)
		}
	})

	t.Run("initialises EnvOverrides map", func(t *testing.T) {
		cfg := &OIDCConfig{}
		cfg.ApplyDefaults("")

		if cfg.EnvOverrides == nil {
			t.Error("EnvOverrides should be initialized")
		}
	})
}

func TestOIDCConfigValidate(t *testing.T) {
	t.Run("nil config is valid", func(t *testing.T) {
		var cfg *OIDCConfig
		if err := cfg.Validate(); err != nil {
			t.Errorf("Validate() = %v, want nil", err)
		}
	})

	t.Run("disabled config is valid", func(t *testing.T) {
		cfg := &OIDCConfig{
			Enabled: false,
		}
		if err := cfg.Validate(); err != nil {
			t.Errorf("Validate() = %v, want nil", err)
		}
	})

	t.Run("enabled requires issuer URL", func(t *testing.T) {
		cfg := &OIDCConfig{
			Enabled:     true,
			ClientID:    "client-123",
			RedirectURL: "https://pulse.example.com/callback",
			Scopes:      []string{"openid"},
		}
		if err := cfg.Validate(); err == nil {
			t.Error("Validate() should fail without issuer URL")
		}
	})

	t.Run("enabled requires valid issuer URL", func(t *testing.T) {
		cfg := &OIDCConfig{
			Enabled:     true,
			IssuerURL:   "not-a-valid-url",
			ClientID:    "client-123",
			RedirectURL: "https://pulse.example.com/callback",
			Scopes:      []string{"openid"},
		}
		if err := cfg.Validate(); err == nil {
			t.Error("Validate() should fail with invalid issuer URL")
		}
	})

	t.Run("enabled requires client ID", func(t *testing.T) {
		cfg := &OIDCConfig{
			Enabled:     true,
			IssuerURL:   "https://issuer.example.com",
			RedirectURL: "https://pulse.example.com/callback",
			Scopes:      []string{"openid"},
		}
		if err := cfg.Validate(); err == nil {
			t.Error("Validate() should fail without client ID")
		}
	})

	t.Run("enabled requires redirect URL", func(t *testing.T) {
		cfg := &OIDCConfig{
			Enabled:   true,
			IssuerURL: "https://issuer.example.com",
			ClientID:  "client-123",
			Scopes:    []string{"openid"},
		}
		if err := cfg.Validate(); err == nil {
			t.Error("Validate() should fail without redirect URL")
		}
	})

	t.Run("enabled requires valid redirect URL", func(t *testing.T) {
		cfg := &OIDCConfig{
			Enabled:     true,
			IssuerURL:   "https://issuer.example.com",
			ClientID:    "client-123",
			RedirectURL: "not-a-valid-url",
			Scopes:      []string{"openid"},
		}
		if err := cfg.Validate(); err == nil {
			t.Error("Validate() should fail with invalid redirect URL")
		}
	})

	t.Run("enabled requires at least one scope", func(t *testing.T) {
		cfg := &OIDCConfig{
			Enabled:     true,
			IssuerURL:   "https://issuer.example.com",
			ClientID:    "client-123",
			RedirectURL: "https://pulse.example.com/callback",
			Scopes:      []string{},
		}
		if err := cfg.Validate(); err == nil {
			t.Error("Validate() should fail without scopes")
		}
	})

	t.Run("valid enabled config", func(t *testing.T) {
		cfg := &OIDCConfig{
			Enabled:     true,
			IssuerURL:   "https://issuer.example.com",
			ClientID:    "client-123",
			RedirectURL: "https://pulse.example.com/callback",
			Scopes:      []string{"openid", "profile"},
		}
		if err := cfg.Validate(); err != nil {
			t.Errorf("Validate() = %v, want nil", err)
		}
	})
}

func TestOIDCConfigMergeFromEnv(t *testing.T) {
	t.Run("nil config", func(t *testing.T) {
		var cfg *OIDCConfig
		cfg.MergeFromEnv(map[string]string{"OIDC_ENABLED": "true"}) // Should not panic
	})

	t.Run("merges all fields", func(t *testing.T) {
		cfg := &OIDCConfig{}
		env := map[string]string{
			"OIDC_ENABLED":         "true",
			"OIDC_ISSUER_URL":      "https://issuer.example.com",
			"OIDC_CLIENT_ID":       "client-123",
			"OIDC_CLIENT_SECRET":   "secret",
			"OIDC_REDIRECT_URL":    "https://pulse.example.com/callback",
			"OIDC_LOGOUT_URL":      "https://issuer.example.com/logout",
			"OIDC_SCOPES":          "openid,profile,email",
			"OIDC_USERNAME_CLAIM":  "sub",
			"OIDC_EMAIL_CLAIM":     "mail",
			"OIDC_GROUPS_CLAIM":    "groups",
			"OIDC_ALLOWED_GROUPS":  "admin,users",
			"OIDC_ALLOWED_DOMAINS": "example.com,test.com",
			"OIDC_ALLOWED_EMAILS":  "user@example.com",
			"OIDC_CA_BUNDLE":       "-----BEGIN CERTIFICATE-----",
		}

		cfg.MergeFromEnv(env)

		if !cfg.Enabled {
			t.Error("Enabled should be true")
		}
		if cfg.IssuerURL != "https://issuer.example.com" {
			t.Errorf("IssuerURL = %q, want %q", cfg.IssuerURL, "https://issuer.example.com")
		}
		if cfg.ClientID != "client-123" {
			t.Errorf("ClientID = %q, want %q", cfg.ClientID, "client-123")
		}
		if cfg.ClientSecret != "secret" {
			t.Errorf("ClientSecret = %q, want %q", cfg.ClientSecret, "secret")
		}
		if cfg.RedirectURL != "https://pulse.example.com/callback" {
			t.Errorf("RedirectURL = %q", cfg.RedirectURL)
		}
		if cfg.LogoutURL != "https://issuer.example.com/logout" {
			t.Errorf("LogoutURL = %q", cfg.LogoutURL)
		}
		if len(cfg.Scopes) != 3 {
			t.Errorf("Scopes = %v, want 3 elements", cfg.Scopes)
		}
		if cfg.UsernameClaim != "sub" {
			t.Errorf("UsernameClaim = %q, want %q", cfg.UsernameClaim, "sub")
		}
		if cfg.EmailClaim != "mail" {
			t.Errorf("EmailClaim = %q, want %q", cfg.EmailClaim, "mail")
		}
		if cfg.GroupsClaim != "groups" {
			t.Errorf("GroupsClaim = %q, want %q", cfg.GroupsClaim, "groups")
		}
		if len(cfg.AllowedGroups) != 2 {
			t.Errorf("AllowedGroups = %v, want 2 elements", cfg.AllowedGroups)
		}
		if len(cfg.AllowedDomains) != 2 {
			t.Errorf("AllowedDomains = %v, want 2 elements", cfg.AllowedDomains)
		}
		if len(cfg.AllowedEmails) != 1 {
			t.Errorf("AllowedEmails = %v, want 1 element", cfg.AllowedEmails)
		}
		if cfg.CABundle != "-----BEGIN CERTIFICATE-----" {
			t.Errorf("CABundle = %q", cfg.CABundle)
		}
	})

	t.Run("tracks env overrides", func(t *testing.T) {
		cfg := &OIDCConfig{}
		env := map[string]string{
			"OIDC_ENABLED":    "true",
			"OIDC_ISSUER_URL": "https://issuer.example.com",
		}

		cfg.MergeFromEnv(env)

		if !cfg.EnvOverrides["enabled"] {
			t.Error("enabled should be marked as env override")
		}
		if !cfg.EnvOverrides["issuerUrl"] {
			t.Error("issuerUrl should be marked as env override")
		}
		if cfg.EnvOverrides["clientId"] {
			t.Error("clientId should not be marked as env override")
		}
	})

	t.Run("enabled with 1", func(t *testing.T) {
		cfg := &OIDCConfig{}
		cfg.MergeFromEnv(map[string]string{"OIDC_ENABLED": "1"})

		if !cfg.Enabled {
			t.Error("Enabled should be true for '1'")
		}
	})

	t.Run("enabled false for other values", func(t *testing.T) {
		cfg := &OIDCConfig{}
		cfg.MergeFromEnv(map[string]string{"OIDC_ENABLED": "false"})

		if cfg.Enabled {
			t.Error("Enabled should be false for 'false'")
		}
	})
}

func TestNewOIDCConfig(t *testing.T) {
	cfg := NewOIDCConfig()

	if cfg == nil {
		t.Fatal("NewOIDCConfig() returned nil")
	}

	// Should have default scopes applied
	if len(cfg.Scopes) != 3 {
		t.Errorf("Scopes = %v, want default 3 scopes", cfg.Scopes)
	}

	// Should have default claims
	if cfg.UsernameClaim != "preferred_username" {
		t.Errorf("UsernameClaim = %q, want %q", cfg.UsernameClaim, "preferred_username")
	}
	if cfg.EmailClaim != "email" {
		t.Errorf("EmailClaim = %q, want %q", cfg.EmailClaim, "email")
	}

	// Should have initialized EnvOverrides
	if cfg.EnvOverrides == nil {
		t.Error("EnvOverrides should be initialized")
	}
}
