//go:build test

package oidc

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"github.com/seaweedfs/seaweedfs/weed/iam/providers"
)

// MockOIDCProvider is a mock implementation for testing
type MockOIDCProvider struct {
	*OIDCProvider
	TestTokens map[string]*providers.TokenClaims
	TestUsers  map[string]*providers.ExternalIdentity
}

// NewMockOIDCProvider creates a mock OIDC provider for testing
func NewMockOIDCProvider(name string) *MockOIDCProvider {
	return &MockOIDCProvider{
		OIDCProvider: NewOIDCProvider(name),
		TestTokens:   make(map[string]*providers.TokenClaims),
		TestUsers:    make(map[string]*providers.ExternalIdentity),
	}
}

// AddTestToken adds a test token with expected claims
func (m *MockOIDCProvider) AddTestToken(token string, claims *providers.TokenClaims) {
	m.TestTokens[token] = claims
}

// AddTestUser adds a test user with expected identity
func (m *MockOIDCProvider) AddTestUser(userID string, identity *providers.ExternalIdentity) {
	m.TestUsers[userID] = identity
}

// Authenticate overrides the parent Authenticate method to use mock data
func (m *MockOIDCProvider) Authenticate(ctx context.Context, token string) (*providers.ExternalIdentity, error) {
	if !m.initialized {
		return nil, fmt.Errorf("provider not initialized")
	}

	if token == "" {
		return nil, fmt.Errorf("token cannot be empty")
	}

	// Validate token using mock validation
	claims, err := m.ValidateToken(ctx, token)
	if err != nil {
		return nil, err
	}

	// Map claims to external identity
	email, _ := claims.GetClaimString("email")
	displayName, _ := claims.GetClaimString("name")
	groups, _ := claims.GetClaimStringSlice("groups")

	return &providers.ExternalIdentity{
		UserID:      claims.Subject,
		Email:       email,
		DisplayName: displayName,
		Groups:      groups,
		Provider:    m.name,
	}, nil
}

// ValidateToken validates tokens using test data
func (m *MockOIDCProvider) ValidateToken(ctx context.Context, token string) (*providers.TokenClaims, error) {
	if !m.initialized {
		return nil, fmt.Errorf("provider not initialized")
	}

	if token == "" {
		return nil, fmt.Errorf("token cannot be empty")
	}

	// Special test tokens
	if token == "expired_token" {
		return nil, fmt.Errorf("token has expired")
	}
	if token == "invalid_token" {
		return nil, fmt.Errorf("invalid token")
	}

	// Try to parse as JWT token first
	if len(token) > 20 && strings.Count(token, ".") >= 2 {
		parsedToken, _, err := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{})
		if err == nil {
			if jwtClaims, ok := parsedToken.Claims.(jwt.MapClaims); ok {
				issuer, _ := jwtClaims["iss"].(string)
				subject, _ := jwtClaims["sub"].(string)
				audience, _ := jwtClaims["aud"].(string)

				// Verify the issuer matches our configuration
				if issuer == m.config.Issuer && subject != "" {
					// Extract expiration and issued at times
					var expiresAt, issuedAt time.Time
					if exp, ok := jwtClaims["exp"].(float64); ok {
						expiresAt = time.Unix(int64(exp), 0)
					}
					if iat, ok := jwtClaims["iat"].(float64); ok {
						issuedAt = time.Unix(int64(iat), 0)
					}

					return &providers.TokenClaims{
						Subject:   subject,
						Issuer:    issuer,
						Audience:  audience,
						ExpiresAt: expiresAt,
						IssuedAt:  issuedAt,
						Claims: map[string]interface{}{
							"email": subject + "@test-domain.com",
							"name":  "Test User " + subject,
						},
					}, nil
				}
			}
		}
	}

	// Check test tokens
	if claims, exists := m.TestTokens[token]; exists {
		return claims, nil
	}

	// Default test token for basic testing
	if token == "valid_test_token" {
		return &providers.TokenClaims{
			Subject:   "test-user-id",
			Issuer:    m.config.Issuer,
			Audience:  m.config.ClientID,
			ExpiresAt: time.Now().Add(time.Hour),
			IssuedAt:  time.Now(),
			Claims: map[string]interface{}{
				"email":  "test@example.com",
				"name":   "Test User",
				"groups": []string{"developers", "users"},
			},
		}, nil
	}

	return nil, fmt.Errorf("unknown test token: %s", token)
}

// GetUserInfo returns test user info
func (m *MockOIDCProvider) GetUserInfo(ctx context.Context, userID string) (*providers.ExternalIdentity, error) {
	if !m.initialized {
		return nil, fmt.Errorf("provider not initialized")
	}

	if userID == "" {
		return nil, fmt.Errorf("user ID cannot be empty")
	}

	// Check test users
	if identity, exists := m.TestUsers[userID]; exists {
		return identity, nil
	}

	// Default test user
	return &providers.ExternalIdentity{
		UserID:      userID,
		Email:       userID + "@example.com",
		DisplayName: "Test User " + userID,
		Provider:    m.name,
	}, nil
}

// SetupDefaultTestData configures common test data
func (m *MockOIDCProvider) SetupDefaultTestData() {
	// Create default token claims
	defaultClaims := &providers.TokenClaims{
		Subject:   "test-user-123",
		Issuer:    "https://test-issuer.com",
		Audience:  "test-client-id",
		ExpiresAt: time.Now().Add(time.Hour),
		IssuedAt:  time.Now(),
		Claims: map[string]interface{}{
			"email":  "testuser@example.com",
			"name":   "Test User",
			"groups": []string{"developers"},
		},
	}

	// Add multiple token variants for compatibility
	m.AddTestToken("valid_token", defaultClaims)
	m.AddTestToken("valid-oidc-token", defaultClaims) // For integration tests
	m.AddTestToken("valid_test_token", defaultClaims) // For STS tests

	// Add default test users
	m.AddTestUser("test-user-123", &providers.ExternalIdentity{
		UserID:      "test-user-123",
		Email:       "testuser@example.com",
		DisplayName: "Test User",
		Groups:      []string{"developers"},
		Provider:    m.name,
	})
}
