package providers

import (
	"net/url"
	"testing"
)

// TestGoogleProvider_NewGoogleProvider tests the constructor
func TestGoogleProvider_NewGoogleProvider(t *testing.T) {
	provider := NewGoogleProvider()

	if provider == nil {
		t.Fatal("Expected provider to be created, got nil")
	}

	if provider.BaseProvider == nil {
		t.Error("BaseProvider should be initialized")
	}
}

// TestGoogleProvider_GetType tests provider type
func TestGoogleProvider_GetType(t *testing.T) {
	provider := NewGoogleProvider()

	if provider.GetType() != ProviderTypeGoogle {
		t.Errorf("Expected ProviderTypeGoogle, got %v", provider.GetType())
	}
}

// TestGoogleProvider_GetCapabilities tests Google-specific capabilities
func TestGoogleProvider_GetCapabilities(t *testing.T) {
	provider := NewGoogleProvider()
	capabilities := provider.GetCapabilities()

	if !capabilities.SupportsRefreshTokens {
		t.Error("Expected SupportsRefreshTokens to be true")
	}

	if capabilities.RequiresOfflineAccessScope {
		t.Error("Expected RequiresOfflineAccessScope to be false for Google")
	}

	if !capabilities.RequiresPromptConsent {
		t.Error("Expected RequiresPromptConsent to be true for Google")
	}

	if capabilities.PreferredTokenValidation != "id" {
		t.Errorf("Expected PreferredTokenValidation 'id', got '%s'", capabilities.PreferredTokenValidation)
	}
}

// TestGoogleProvider_BuildAuthParams tests Google-specific auth parameters
func TestGoogleProvider_BuildAuthParams(t *testing.T) {
	provider := NewGoogleProvider()

	tests := []struct {
		name                 string
		inputScopes          []string
		expectedScopes       []string
		shouldHaveAccessType bool
		shouldHavePrompt     bool
	}{
		{
			name:                 "Basic scopes without offline_access",
			inputScopes:          []string{"openid", "profile", "email"},
			expectedScopes:       []string{"openid", "profile", "email"},
			shouldHaveAccessType: true,
			shouldHavePrompt:     true,
		},
		{
			name:                 "Scopes with offline_access (should be filtered out)",
			inputScopes:          []string{"openid", "profile", "offline_access", "email"},
			expectedScopes:       []string{"openid", "profile", "email"},
			shouldHaveAccessType: true,
			shouldHavePrompt:     true,
		},
		{
			name:                 "Only offline_access scope (should be filtered out)",
			inputScopes:          []string{"offline_access"},
			expectedScopes:       []string{},
			shouldHaveAccessType: true,
			shouldHavePrompt:     true,
		},
		{
			name:                 "Empty scopes",
			inputScopes:          []string{},
			expectedScopes:       []string{},
			shouldHaveAccessType: true,
			shouldHavePrompt:     true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			baseParams := make(url.Values)
			baseParams.Set("client_id", "test-client")

			result, err := provider.BuildAuthParams(baseParams, tt.inputScopes)

			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}

			// Check Google-specific parameters
			if tt.shouldHaveAccessType {
				if result.URLValues.Get("access_type") != "offline" {
					t.Errorf("Expected access_type 'offline', got '%s'", result.URLValues.Get("access_type"))
				}
			}

			if tt.shouldHavePrompt {
				if result.URLValues.Get("prompt") != "consent" {
					t.Errorf("Expected prompt 'consent', got '%s'", result.URLValues.Get("prompt"))
				}
			}

			// Check filtered scopes
			if len(result.Scopes) != len(tt.expectedScopes) {
				t.Errorf("Expected %d scopes, got %d", len(tt.expectedScopes), len(result.Scopes))
			}

			for _, expectedScope := range tt.expectedScopes {
				found := false
				for _, actualScope := range result.Scopes {
					if actualScope == expectedScope {
						found = true
						break
					}
				}
				if !found {
					t.Errorf("Expected scope '%s' not found in result", expectedScope)
				}
			}

			// Ensure offline_access is not in the result scopes
			for _, scope := range result.Scopes {
				if scope == "offline_access" {
					t.Error("offline_access scope should be filtered out for Google")
				}
			}

			// Verify original base parameters are preserved
			if result.URLValues.Get("client_id") != "test-client" {
				t.Errorf("Expected client_id 'test-client', got '%s'", result.URLValues.Get("client_id"))
			}
		})
	}
}

// TestGoogleProvider_ValidateConfig tests configuration validation
func TestGoogleProvider_ValidateConfig(t *testing.T) {
	provider := NewGoogleProvider()

	err := provider.ValidateConfig()

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

// TestGoogleProvider_InterfaceCompliance tests that Google provider implements OIDCProvider
func TestGoogleProvider_InterfaceCompliance(t *testing.T) {
	provider := NewGoogleProvider()

	// Verify it implements the OIDCProvider interface
	var _ OIDCProvider = provider
}

// TestGoogleProvider_OfflineAccessFiltering tests comprehensive offline_access filtering
func TestGoogleProvider_OfflineAccessFiltering(t *testing.T) {
	provider := NewGoogleProvider()

	tests := []struct {
		name        string
		description string
		inputScopes []string
	}{
		{
			name:        "Multiple offline_access occurrences",
			inputScopes: []string{"openid", "offline_access", "profile", "offline_access", "email"},
			description: "Should remove all instances of offline_access",
		},
		{
			name:        "Case sensitive filtering",
			inputScopes: []string{"openid", "OFFLINE_ACCESS", "profile", "offline_access"},
			description: "Should only remove exact case matches",
		},
		{
			name:        "Similar but different scopes",
			inputScopes: []string{"openid", "offline_access_extended", "profile", "offline_access"},
			description: "Should only remove exact offline_access matches",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			baseParams := make(url.Values)
			result, err := provider.BuildAuthParams(baseParams, tt.inputScopes)

			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}

			// Count offline_access occurrences in result
			offlineAccessCount := 0
			for _, scope := range result.Scopes {
				if scope == "offline_access" {
					offlineAccessCount++
				}
			}

			if offlineAccessCount != 0 {
				t.Errorf("Expected 0 offline_access scopes in result, got %d", offlineAccessCount)
			}

			// Verify other scopes are preserved
			for _, originalScope := range tt.inputScopes {
				if originalScope == "offline_access" {
					continue // Skip the filtered scope
				}

				found := false
				for _, resultScope := range result.Scopes {
					if resultScope == originalScope {
						found = true
						break
					}
				}
				if !found {
					t.Errorf("Expected scope '%s' to be preserved", originalScope)
				}
			}
		})
	}
}

// TestGoogleProvider_BaseProviderInheritance tests inherited functionality from BaseProvider
func TestGoogleProvider_BaseProviderInheritance(t *testing.T) {
	provider := NewGoogleProvider()

	// Test ValidateTokens (inherited from BaseProvider)
	session := &mockSession{
		authenticated: true,
		idToken:       "test-token",
		accessToken:   "access-token", // Add access token for proper validation
	}
	verifier := &mockTokenVerifier{}
	cache := &mockTokenCache{
		claims: map[string]map[string]interface{}{
			"test-token": {
				"exp": float64(9999999999), // Far future
				"sub": "user123",
			},
		},
	}

	result, err := provider.ValidateTokens(session, verifier, cache, 0)
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}

	if !result.Authenticated {
		t.Error("Expected result to be authenticated")
	}

	// Test HandleTokenRefresh (inherited from BaseProvider)
	tokenData := &TokenResult{IDToken: "new-token"}
	err = provider.HandleTokenRefresh(tokenData)
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}
}

// TestGoogleProvider_AuthParamsPreservation tests that base parameters are not overwritten
func TestGoogleProvider_AuthParamsPreservation(t *testing.T) {
	provider := NewGoogleProvider()

	baseParams := make(url.Values)
	baseParams.Set("client_id", "test-client")
	baseParams.Set("redirect_uri", "https://example.com/callback")
	baseParams.Set("response_type", "code")
	baseParams.Set("state", "test-state")
	baseParams.Set("nonce", "test-nonce")

	scopes := []string{"openid", "profile"}

	result, err := provider.BuildAuthParams(baseParams, scopes)
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
	}

	// Verify all original parameters are preserved
	expectedParams := map[string]string{
		"client_id":     "test-client",
		"redirect_uri":  "https://example.com/callback",
		"response_type": "code",
		"state":         "test-state",
		"nonce":         "test-nonce",
		"access_type":   "offline", // Added by Google provider
		"prompt":        "consent", // Added by Google provider
	}

	for key, expectedValue := range expectedParams {
		actualValue := result.URLValues.Get(key)
		if actualValue != expectedValue {
			t.Errorf("Expected %s '%s', got '%s'", key, expectedValue, actualValue)
		}
	}

	// Verify scopes
	if len(result.Scopes) != 2 {
		t.Errorf("Expected 2 scopes, got %d", len(result.Scopes))
	}

	expectedScopes := []string{"openid", "profile"}
	for _, expectedScope := range expectedScopes {
		found := false
		for _, actualScope := range result.Scopes {
			if actualScope == expectedScope {
				found = true
				break
			}
		}
		if !found {
			t.Errorf("Expected scope '%s' not found", expectedScope)
		}
	}
}

// Benchmark tests
func BenchmarkGoogleProvider_BuildAuthParams(b *testing.B) {
	provider := NewGoogleProvider()
	baseParams := make(url.Values)
	baseParams.Set("client_id", "test-client")
	scopes := []string{"openid", "profile", "email", "offline_access"}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		provider.BuildAuthParams(baseParams, scopes)
	}
}

func BenchmarkGoogleProvider_GetCapabilities(b *testing.B) {
	provider := NewGoogleProvider()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		provider.GetCapabilities()
	}
}
