package helper

import (
	"authentication/models"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

func TestExtractEmailFromToken_ValidAccessToken(t *testing.T) {
	// Set up test environment
	secretKey := "test-secret-key-123"
	os.Setenv("JWT_SECRET_KEY", secretKey)
	defer os.Unsetenv("JWT_SECRET_KEY")

	email := "test@example.com"

	// Create valid AccessToken
	claims := &models.AccessToken{
		Email:     email,
		SessionID: "session123",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString([]byte(secretKey))
	if err != nil {
		t.Fatalf("Failed to sign token: %v", err)
	}

	// Test extraction
	extractedEmail, err := ExtractEmailFromToken(tokenString)
	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if extractedEmail != email {
		t.Errorf("Expected email '%s', got '%s'", email, extractedEmail)
	}
}

func TestExtractEmailFromToken_ValidMapClaims(t *testing.T) {
	secretKey := "test-secret-key-456"
	os.Setenv("JWT_SECRET_KEY", secretKey)
	defer os.Unsetenv("JWT_SECRET_KEY")

	email := "mapuser@example.com"

	// Create token with MapClaims
	claims := jwt.MapClaims{
		"email": email,
		"exp":   time.Now().Add(time.Hour).Unix(),
		"iat":   time.Now().Unix(),
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString([]byte(secretKey))
	if err != nil {
		t.Fatalf("Failed to sign token: %v", err)
	}

	// Test extraction
	extractedEmail, err := ExtractEmailFromToken(tokenString)
	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if extractedEmail != email {
		t.Errorf("Expected email '%s', got '%s'", email, extractedEmail)
	}
}

func TestExtractEmailFromToken_BearerPrefix(t *testing.T) {
	secretKey := "test-secret-bearer"
	os.Setenv("JWT_SECRET_KEY", secretKey)
	defer os.Unsetenv("JWT_SECRET_KEY")

	email := "bearer@example.com"

	claims := &models.AccessToken{
		Email:     email,
		SessionID: "session789",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString([]byte(secretKey))
	if err != nil {
		t.Fatalf("Failed to sign token: %v", err)
	}

	// Test with Bearer prefix
	bearerToken := "Bearer " + tokenString
	extractedEmail, err := ExtractEmailFromToken(bearerToken)
	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if extractedEmail != email {
		t.Errorf("Expected email '%s', got '%s'", email, extractedEmail)
	}
}

func TestExtractEmailFromToken_EmptyToken(t *testing.T) {
	os.Setenv("JWT_SECRET_KEY", "test-key")
	defer os.Unsetenv("JWT_SECRET_KEY")

	testCases := []string{"", "null", "undefined"}

	for _, tokenString := range testCases {
		_, err := ExtractEmailFromToken(tokenString)
		if err == nil {
			t.Errorf("Expected error for token '%s', got nil", tokenString)
		}

		if !strings.Contains(err.Error(), "no valid token provided") {
			t.Errorf("Expected 'no valid token provided' error, got: %v", err)
		}
	}
}

func TestExtractEmailFromToken_InvalidSignature(t *testing.T) {
	os.Setenv("JWT_SECRET_KEY", "correct-secret")
	defer os.Unsetenv("JWT_SECRET_KEY")

	// Create token with wrong secret
	wrongSecret := "wrong-secret"
	claims := &models.AccessToken{
		Email:     "test@example.com",
		SessionID: "session123",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, _ := token.SignedString([]byte(wrongSecret))

	// Try to extract with different secret
	_, err := ExtractEmailFromToken(tokenString)
	if err == nil {
		t.Error("Expected error for invalid signature")
	}

	if !strings.Contains(err.Error(), "invalid token signature") {
		t.Errorf("Expected 'invalid token signature' error, got: %v", err)
	}
}

func TestExtractEmailFromToken_ExpiredToken(t *testing.T) {
	secretKey := "test-expired-key"
	os.Setenv("JWT_SECRET_KEY", secretKey)
	defer os.Unsetenv("JWT_SECRET_KEY")

	// Create expired token
	claims := &models.AccessToken{
		Email:     "expired@example.com",
		SessionID: "session999",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(-time.Hour)), // Expired 1 hour ago
			IssuedAt:  jwt.NewNumericDate(time.Now().Add(-2 * time.Hour)),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, _ := token.SignedString([]byte(secretKey))

	// Note: The ExtractEmailFromToken function doesn't validate expiration,
	// it relies on ParseWithClaims which may or may not enforce expiration
	// depending on jwt library version. We'll just verify it can extract the email.
	extractedEmail, _ := ExtractEmailFromToken(tokenString)
	if extractedEmail != "expired@example.com" {
		t.Logf("Extracted email from expired token: %s", extractedEmail)
	}
}

func TestExtractEmailFromToken_NoEmailInClaims(t *testing.T) {
	secretKey := "test-no-email"
	os.Setenv("JWT_SECRET_KEY", secretKey)
	defer os.Unsetenv("JWT_SECRET_KEY")

	// Create token without email
	claims := jwt.MapClaims{
		"user_id": "user123",
		"exp":     time.Now().Add(time.Hour).Unix(),
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, _ := token.SignedString([]byte(secretKey))

	_, err := ExtractEmailFromToken(tokenString)
	if err == nil {
		t.Error("Expected error for token without email")
	}

	if !strings.Contains(err.Error(), "email not found in token") {
		t.Errorf("Expected 'email not found in token' error, got: %v", err)
	}
}

func TestExtractEmailFromToken_InvalidEmailFormat(t *testing.T) {
	secretKey := "test-invalid-email"
	os.Setenv("JWT_SECRET_KEY", secretKey)
	defer os.Unsetenv("JWT_SECRET_KEY")

	// Create token with invalid email (no @ symbol)
	claims := &models.AccessToken{
		Email:     "notanemail",
		SessionID: "session123",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, _ := token.SignedString([]byte(secretKey))

	_, err := ExtractEmailFromToken(tokenString)
	if err == nil {
		t.Error("Expected error for invalid email format")
	}
}

func TestExtractEmailFromToken_NoSecretKey(t *testing.T) {
	// Ensure no secret key is set
	os.Unsetenv("JWT_SECRET_KEY")

	claims := &models.AccessToken{
		Email:     "test@example.com",
		SessionID: "session123",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, _ := token.SignedString([]byte("any-key"))

	_, err := ExtractEmailFromToken(tokenString)
	if err == nil {
		t.Error("Expected error when secret key not set")
	}
}

func TestExtractEmailFromToken_WrongSigningMethod(t *testing.T) {
	secretKey := "test-wrong-method"
	os.Setenv("JWT_SECRET_KEY", secretKey)
	defer os.Unsetenv("JWT_SECRET_KEY")

	// Try to create token with non-HMAC signing method (would need RSA keys in real scenario)
	// For simplicity, we'll create a malformed token string
	malformedToken := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20ifQ.invalid"

	_, err := ExtractEmailFromToken(malformedToken)
	if err == nil {
		t.Error("Expected error for wrong signing method")
	}
}

func TestExtractEmailFromToken_MalformedToken(t *testing.T) {
	os.Setenv("JWT_SECRET_KEY", "test-key")
	defer os.Unsetenv("JWT_SECRET_KEY")

	malformedTokens := []string{
		"not.a.token",
		"invalid",
		"Bearer invalid",
		"...",
		"a.b",
	}

	for _, tokenString := range malformedTokens {
		_, err := ExtractEmailFromToken(tokenString)
		if err == nil {
			t.Errorf("Expected error for malformed token '%s'", tokenString)
		}
	}
}

func TestExtractEmailFromToken_MultipleAtSymbols(t *testing.T) {
	secretKey := "test-multiple-at"
	os.Setenv("JWT_SECRET_KEY", secretKey)
	defer os.Unsetenv("JWT_SECRET_KEY")

	email := "user@sub@example.com"

	claims := &models.AccessToken{
		Email:     email,
		SessionID: "session123",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, _ := token.SignedString([]byte(secretKey))

	// Should extract successfully (just checks for @ presence)
	extractedEmail, err := ExtractEmailFromToken(tokenString)
	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if extractedEmail != email {
		t.Errorf("Expected email '%s', got '%s'", email, extractedEmail)
	}
}

func TestExtractEmailFromToken_WhitespaceEmail(t *testing.T) {
	secretKey := "test-whitespace"
	os.Setenv("JWT_SECRET_KEY", secretKey)
	defer os.Unsetenv("JWT_SECRET_KEY")

	// Email with spaces (should still work if it has @)
	email := "  user@example.com  "

	claims := jwt.MapClaims{
		"email": email,
		"exp":   time.Now().Add(time.Hour).Unix(),
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, _ := token.SignedString([]byte(secretKey))

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

	if extractedEmail != email {
		t.Errorf("Expected email '%s', got '%s'", email, extractedEmail)
	}
}

func TestExtractEmailFromToken_CaseInsensitiveBearer(t *testing.T) {
	secretKey := "test-case-bearer"
	os.Setenv("JWT_SECRET_KEY", secretKey)
	defer os.Unsetenv("JWT_SECRET_KEY")

	email := "case@example.com"

	claims := &models.AccessToken{
		Email:     email,
		SessionID: "session123",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, _ := token.SignedString([]byte(secretKey))

	// Test with standard "Bearer " prefix
	bearerToken := "Bearer " + tokenString
	extractedEmail, err := ExtractEmailFromToken(bearerToken)
	if err != nil {
		t.Errorf("Expected no error for Bearer prefix, got: %v", err)
	}

	if extractedEmail != email {
		t.Errorf("Expected email '%s', got '%s'", email, extractedEmail)
	}
}

func BenchmarkExtractEmailFromToken(b *testing.B) {
	secretKey := "benchmark-secret"
	os.Setenv("JWT_SECRET_KEY", secretKey)
	defer os.Unsetenv("JWT_SECRET_KEY")

	claims := &models.AccessToken{
		Email:     "bench@example.com",
		SessionID: "sessionBench",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, _ := token.SignedString([]byte(secretKey))

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