package services

import (
	"errors"
	"fmt"
	"slices"
	"testing"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"gorm.io/gorm"
	"goyco/internal/config"
	"goyco/internal/database"
)

type jwtMockUserRepo struct {
	users map[uint]*database.User
}

func (m *jwtMockUserRepo) GetByID(id uint) (*database.User, error) {
	if user, exists := m.users[id]; exists {
		return user, nil
	}
	return nil, gorm.ErrRecordNotFound
}

func (m *jwtMockUserRepo) GetByUsername(username string) (*database.User, error) {
	for _, user := range m.users {
		if user.Username == username {
			return user, nil
		}
	}
	return nil, gorm.ErrRecordNotFound
}

func (m *jwtMockUserRepo) Update(user *database.User) error {
	if _, exists := m.users[user.ID]; !exists {
		return gorm.ErrRecordNotFound
	}
	m.users[user.ID] = user
	return nil
}

type jwtMockRefreshTokenRepo struct {
	tokens map[string]*database.RefreshToken
	nextID uint
}

func (m *jwtMockRefreshTokenRepo) Create(token *database.RefreshToken) error {
	if m.tokens == nil {
		m.tokens = make(map[string]*database.RefreshToken)
	}
	m.nextID++
	token.ID = m.nextID
	m.tokens[token.TokenHash] = token
	return nil
}

func (m *jwtMockRefreshTokenRepo) GetByTokenHash(tokenHash string) (*database.RefreshToken, error) {
	if token, exists := m.tokens[tokenHash]; exists {
		return token, nil
	}
	return nil, gorm.ErrRecordNotFound
}

func (m *jwtMockRefreshTokenRepo) DeleteByUserID(userID uint) error {
	for hash, token := range m.tokens {
		if token.UserID == userID {
			delete(m.tokens, hash)
		}
	}
	return nil
}

func (m *jwtMockRefreshTokenRepo) DeleteExpired() error {
	now := time.Now()
	for hash, token := range m.tokens {
		if token.ExpiresAt.Before(now) {
			delete(m.tokens, hash)
		}
	}
	return nil
}

func (m *jwtMockRefreshTokenRepo) DeleteByID(id uint) error {
	for hash, token := range m.tokens {
		if token.ID == id {
			delete(m.tokens, hash)
			return nil
		}
	}
	return gorm.ErrRecordNotFound
}

func (m *jwtMockRefreshTokenRepo) GetByUserID(userID uint) ([]database.RefreshToken, error) {
	var tokens []database.RefreshToken
	for _, token := range m.tokens {
		if token.UserID == userID {
			tokens = append(tokens, *token)
		}
	}
	return tokens, nil
}

func (m *jwtMockRefreshTokenRepo) CountByUserID(userID uint) (int64, error) {
	var count int64
	for _, token := range m.tokens {
		if token.UserID == userID {
			count++
		}
	}
	return count, nil
}

func createTestJWTService() (*JWTService, *jwtMockUserRepo, *jwtMockRefreshTokenRepo) {
	cfg := &config.JWTConfig{
		Secret:            "test-secret-key-that-is-long-enough-for-security",
		Expiration:        1,
		RefreshExpiration: 24,
		Issuer:            "test-issuer",
		Audience:          "test-audience",
		KeyRotation: config.KeyRotationConfig{
			Enabled:     false,
			CurrentKey:  "",
			PreviousKey: "",
			KeyID:       "",
		},
	}

	userRepo := &jwtMockUserRepo{
		users: make(map[uint]*database.User),
	}
	refreshRepo := &jwtMockRefreshTokenRepo{
		tokens: make(map[string]*database.RefreshToken),
	}

	jwtService := NewJWTService(cfg, userRepo, refreshRepo)
	return jwtService, userRepo, refreshRepo
}

func createTestUser() *database.User {
	return &database.User{
		ID:             1,
		Username:       "testuser",
		Email:          "test@example.com",
		Password:       "hashedpassword",
		EmailVerified:  true,
		Locked:         false,
		SessionVersion: 1,
		CreatedAt:      time.Now(),
		UpdatedAt:      time.Now(),
	}
}

func TestJWTService_GenerateAccessToken(t *testing.T) {
	jwtService, userRepo, _ := createTestJWTService()
	user := createTestUser()
	userRepo.users[user.ID] = user

	t.Run("Successful_Generation", func(t *testing.T) {
		token, err := jwtService.GenerateAccessToken(user)
		if err != nil {
			t.Fatalf("Expected successful token generation, got error: %v", err)
		}

		if token == "" {
			t.Error("Expected non-empty token")
		}

		parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (any, error) {
			return []byte(jwtService.config.Secret), nil
		})
		if err != nil {
			t.Fatalf("Failed to parse generated token: %v", err)
		}

		if !parsedToken.Valid {
			t.Error("Generated token should be valid")
		}

		if claims, ok := parsedToken.Claims.(jwt.MapClaims); ok {
			if claims["sub"] != float64(user.ID) {
				t.Errorf("Expected subject %d, got %v", user.ID, claims["sub"])
			}
			if claims["username"] != user.Username {
				t.Errorf("Expected username %s, got %v", user.Username, claims["username"])
			}
			if claims["session_version"] != float64(user.SessionVersion) {
				t.Errorf("Expected session_version %d, got %v", user.SessionVersion, claims["session_version"])
			}
			if claims["type"] != TokenTypeAccess {
				t.Errorf("Expected type %s, got %v", TokenTypeAccess, claims["type"])
			}
			if claims["iss"] != jwtService.config.Issuer {
				t.Errorf("Expected issuer %s, got %v", jwtService.config.Issuer, claims["iss"])
			}
			if aud, ok := claims["aud"].([]any); !ok || len(aud) != 1 || aud[0] != jwtService.config.Audience {
				t.Errorf("Expected audience [%s], got %v", jwtService.config.Audience, claims["aud"])
			}
		}
	})

	t.Run("Nil_User", func(t *testing.T) {
		_, err := jwtService.GenerateAccessToken(nil)
		if err == nil {
			t.Error("Expected error for nil user")
		}
		if !errors.Is(err, ErrInvalidCredentials) {
			t.Errorf("Expected ErrInvalidCredentials, got %v", err)
		}
	})
}

func TestJWTService_GenerateRefreshToken(t *testing.T) {
	jwtService, userRepo, refreshRepo := createTestJWTService()
	user := createTestUser()
	userRepo.users[user.ID] = user

	t.Run("Successful_Generation", func(t *testing.T) {
		token, err := jwtService.GenerateRefreshToken(user)
		if err != nil {
			t.Fatalf("Expected successful refresh token generation, got error: %v", err)
		}

		if token == "" {
			t.Error("Expected non-empty refresh token")
		}

		tokenHash := jwtService.hashToken(token)
		storedToken, err := refreshRepo.GetByTokenHash(tokenHash)
		if err != nil {
			t.Fatalf("Expected refresh token to be stored in database: %v", err)
		}

		if storedToken.UserID != user.ID {
			t.Errorf("Expected user ID %d, got %d", user.ID, storedToken.UserID)
		}

		expectedExpiry := time.Now().Add(time.Duration(jwtService.config.RefreshExpiration) * time.Hour)
		if storedToken.ExpiresAt.Before(expectedExpiry.Add(-time.Minute)) || storedToken.ExpiresAt.After(expectedExpiry.Add(time.Minute)) {
			t.Errorf("Expected expiry around %v, got %v", expectedExpiry, storedToken.ExpiresAt)
		}
	})

	t.Run("Nil_User", func(t *testing.T) {
		_, err := jwtService.GenerateRefreshToken(nil)
		if err == nil {
			t.Error("Expected error for nil user")
		}
		if !errors.Is(err, ErrInvalidCredentials) {
			t.Errorf("Expected ErrInvalidCredentials, got %v", err)
		}
	})
}

func TestJWTService_VerifyAccessToken(t *testing.T) {
	jwtService, userRepo, _ := createTestJWTService()
	user := createTestUser()
	userRepo.users[user.ID] = user

	t.Run("Valid_Token", func(t *testing.T) {
		token, err := jwtService.GenerateAccessToken(user)
		if err != nil {
			t.Fatalf("Failed to generate token: %v", err)
		}

		userID, err := jwtService.VerifyAccessToken(token)
		if err != nil {
			t.Fatalf("Expected successful token verification, got error: %v", err)
		}

		if userID != user.ID {
			t.Errorf("Expected user ID %d, got %d", user.ID, userID)
		}
	})

	t.Run("Invalid_Token", func(t *testing.T) {
		_, err := jwtService.VerifyAccessToken("invalid-token")
		if err == nil {
			t.Error("Expected error for invalid token")
		}
	})

	t.Run("Empty_Token", func(t *testing.T) {
		_, err := jwtService.VerifyAccessToken("")
		if err == nil {
			t.Error("Expected error for empty token")
		}
		if !errors.Is(err, ErrInvalidToken) {
			t.Errorf("Expected ErrInvalidToken, got %v", err)
		}
	})

	t.Run("User_Not_Found", func(t *testing.T) {
		token, err := jwtService.GenerateAccessToken(user)
		if err != nil {
			t.Fatalf("Failed to generate token: %v", err)
		}

		delete(userRepo.users, user.ID)

		_, err = jwtService.VerifyAccessToken(token)
		if err == nil {
			t.Error("Expected error for non-existent user")
		}
		if !errors.Is(err, ErrInvalidToken) {
			t.Errorf("Expected ErrInvalidToken, got %v", err)
		}
	})

	t.Run("Locked_User", func(t *testing.T) {
		user.Locked = true
		userRepo.users[user.ID] = user

		token, err := jwtService.GenerateAccessToken(user)
		if err != nil {
			t.Fatalf("Failed to generate token: %v", err)
		}

		_, err = jwtService.VerifyAccessToken(token)
		if err == nil {
			t.Error("Expected error for locked user")
		}
		if !errors.Is(err, ErrAccountLocked) {
			t.Errorf("Expected ErrAccountLocked, got %v", err)
		}
	})

	t.Run("Session_Version_Mismatch", func(t *testing.T) {
		user.Locked = false
		user.SessionVersion = 2
		userRepo.users[user.ID] = user

		oldUser := *user
		oldUser.SessionVersion = 1
		token, err := jwtService.GenerateAccessToken(&oldUser)
		if err != nil {
			t.Fatalf("Failed to generate token: %v", err)
		}

		_, err = jwtService.VerifyAccessToken(token)
		if err == nil {
			t.Error("Expected error for session version mismatch")
		}
		if !errors.Is(err, ErrInvalidToken) {
			t.Errorf("Expected ErrInvalidToken, got %v", err)
		}
	})
}

func TestJWTService_RefreshAccessTokenWithRotation(t *testing.T) {
	jwtService, userRepo, refreshRepo := createTestJWTService()
	user := createTestUser()
	userRepo.users[user.ID] = user

	refreshToken, err := jwtService.GenerateRefreshToken(user)
	if err != nil {
		t.Fatalf("Failed to generate refresh token: %v", err)
	}

	accessToken, newRefreshToken, err := jwtService.RefreshAccessTokenWithRotation(refreshToken)
	if err != nil {
		t.Fatalf("Expected successful token refresh, got error: %v", err)
	}

	if accessToken == "" {
		t.Error("Expected non-empty access token")
	}
	if newRefreshToken == "" {
		t.Error("Expected non-empty refresh token")
	}
	if newRefreshToken == refreshToken {
		t.Error("Expected refresh token to rotate")
	}

	userID, err := jwtService.VerifyAccessToken(accessToken)
	if err != nil {
		t.Fatalf("Expected valid access token, got error: %v", err)
	}
	if userID != user.ID {
		t.Errorf("Expected user ID %d, got %d", user.ID, userID)
	}

	_, _, err = jwtService.RefreshAccessTokenWithRotation(refreshToken)
	if err == nil {
		t.Fatal("Expected error for rotated refresh token")
	}
	if !errors.Is(err, ErrRefreshTokenInvalid) {
		t.Errorf("Expected ErrRefreshTokenInvalid, got %v", err)
	}

	_, _, err = jwtService.RefreshAccessTokenWithRotation(newRefreshToken)
	if err != nil {
		t.Fatalf("Expected new refresh token to be usable, got error: %v", err)
	}

	t.Run("Invalid_Refresh_Token", func(t *testing.T) {
		_, _, err := jwtService.RefreshAccessTokenWithRotation("invalid-refresh-token")
		if err == nil {
			t.Error("Expected error for invalid refresh token")
		}
		if !errors.Is(err, ErrRefreshTokenInvalid) {
			t.Errorf("Expected ErrRefreshTokenInvalid, got %v", err)
		}
	})

	t.Run("Expired_Refresh_Token", func(t *testing.T) {
		refreshToken := &database.RefreshToken{
			UserID:    user.ID,
			TokenHash: "expired-token-hash",
			ExpiresAt: time.Now().Add(-time.Hour),
		}
		refreshRepo.tokens["expired-token-hash"] = refreshToken

		testToken := "test-expired-token"
		tokenHash := jwtService.hashToken(testToken)
		refreshToken.TokenHash = tokenHash
		refreshRepo.tokens[tokenHash] = refreshToken

		_, _, err := jwtService.RefreshAccessTokenWithRotation(testToken)
		if err == nil {
			t.Error("Expected error for expired refresh token")
		}
		if !errors.Is(err, ErrRefreshTokenExpired) {
			t.Errorf("Expected ErrRefreshTokenExpired, got %v", err)
		}
	})

	t.Run("User_Not_Found", func(t *testing.T) {
		refreshToken, err := jwtService.GenerateRefreshToken(user)
		if err != nil {
			t.Fatalf("Failed to generate refresh token: %v", err)
		}

		delete(userRepo.users, user.ID)

		_, _, err = jwtService.RefreshAccessTokenWithRotation(refreshToken)
		if err == nil {
			t.Error("Expected error for non-existent user")
		}
		if !errors.Is(err, ErrRefreshTokenInvalid) {
			t.Errorf("Expected ErrRefreshTokenInvalid, got %v", err)
		}
	})

	t.Run("Locked_User", func(t *testing.T) {
		user.Locked = true
		userRepo.users[user.ID] = user

		refreshToken, err := jwtService.GenerateRefreshToken(user)
		if err != nil {
			t.Fatalf("Failed to generate refresh token: %v", err)
		}

		_, _, err = jwtService.RefreshAccessTokenWithRotation(refreshToken)
		if err == nil {
			t.Error("Expected error for locked user")
		}
		if !errors.Is(err, ErrAccountLocked) {
			t.Errorf("Expected ErrAccountLocked, got %v", err)
		}
	})
}

func TestJWTService_RevokeRefreshToken(t *testing.T) {
	jwtService, userRepo, refreshRepo := createTestJWTService()
	user := createTestUser()
	userRepo.users[user.ID] = user

	t.Run("Successful_Revocation", func(t *testing.T) {

		refreshToken, err := jwtService.GenerateRefreshToken(user)
		if err != nil {
			t.Fatalf("Failed to generate refresh token: %v", err)
		}

		tokenHash := jwtService.hashToken(refreshToken)
		_, err = refreshRepo.GetByTokenHash(tokenHash)
		if err != nil {
			t.Fatalf("Expected refresh token to exist: %v", err)
		}

		err = jwtService.RevokeRefreshToken(refreshToken)
		if err != nil {
			t.Fatalf("Expected successful token revocation, got error: %v", err)
		}

		_, err = refreshRepo.GetByTokenHash(tokenHash)
		if err == nil {
			t.Error("Expected refresh token to be removed")
		}
	})

	t.Run("Non_Existent_Token", func(t *testing.T) {
		err := jwtService.RevokeRefreshToken("non-existent-token")
		if err != nil {
			t.Errorf("Expected no error for non-existent token, got %v", err)
		}
	})
}

func TestJWTService_RevokeAllRefreshTokens(t *testing.T) {
	jwtService, userRepo, refreshRepo := createTestJWTService()
	user := createTestUser()
	userRepo.users[user.ID] = user

	t.Run("Successful_Revocation", func(t *testing.T) {

		_, err := jwtService.GenerateRefreshToken(user)
		if err != nil {
			t.Fatalf("Failed to generate first refresh token: %v", err)
		}

		_, err = jwtService.GenerateRefreshToken(user)
		if err != nil {
			t.Fatalf("Failed to generate second refresh token: %v", err)
		}

		count, err := refreshRepo.CountByUserID(user.ID)
		if err != nil {
			t.Fatalf("Failed to count tokens: %v", err)
		}
		if count != 2 {
			t.Errorf("Expected 2 tokens, got %d", count)
		}

		err = jwtService.RevokeAllRefreshTokens(user.ID)
		if err != nil {
			t.Fatalf("Expected successful token revocation, got error: %v", err)
		}

		count, err = refreshRepo.CountByUserID(user.ID)
		if err != nil {
			t.Fatalf("Failed to count tokens: %v", err)
		}
		if count != 0 {
			t.Errorf("Expected 0 tokens, got %d", count)
		}
	})
}

func TestJWTService_CleanupExpiredTokens(t *testing.T) {
	jwtService, userRepo, refreshRepo := createTestJWTService()
	user := createTestUser()
	userRepo.users[user.ID] = user

	t.Run("Successful_Cleanup", func(t *testing.T) {

		expiredToken := &database.RefreshToken{
			UserID:    user.ID,
			TokenHash: "expired-token-hash",
			ExpiresAt: time.Now().Add(-time.Hour),
		}
		refreshRepo.tokens["expired-token-hash"] = expiredToken

		validToken, err := jwtService.GenerateRefreshToken(user)
		if err != nil {
			t.Fatalf("Failed to generate valid refresh token: %v", err)
		}

		if len(refreshRepo.tokens) != 2 {
			t.Errorf("Expected 2 tokens, got %d", len(refreshRepo.tokens))
		}

		err = jwtService.CleanupExpiredTokens()
		if err != nil {
			t.Fatalf("Expected successful cleanup, got error: %v", err)
		}

		if len(refreshRepo.tokens) != 1 {
			t.Errorf("Expected 1 token after cleanup, got %d", len(refreshRepo.tokens))
		}

		tokenHash := jwtService.hashToken(validToken)
		_, exists := refreshRepo.tokens[tokenHash]
		if !exists {
			t.Error("Expected valid token to remain after cleanup")
		}
	})
}

func TestJWTService_KeyRotation(t *testing.T) {
	cfg := &config.JWTConfig{
		Secret:            "old-secret-key-that-is-long-enough-for-security",
		Expiration:        1,
		RefreshExpiration: 24,
		Issuer:            "test-issuer",
		Audience:          "test-audience",
		KeyRotation: config.KeyRotationConfig{
			Enabled:     true,
			CurrentKey:  "current-key-that-is-long-enough-for-security",
			PreviousKey: "previous-key-that-is-long-enough-for-security",
			KeyID:       "current-key-id",
		},
	}

	userRepo := &jwtMockUserRepo{
		users: make(map[uint]*database.User),
	}
	refreshRepo := &jwtMockRefreshTokenRepo{
		tokens: make(map[string]*database.RefreshToken),
	}

	jwtService := NewJWTService(cfg, userRepo, refreshRepo)
	user := createTestUser()
	userRepo.users[user.ID] = user

	t.Run("Generate_Token_With_Key_Rotation", func(t *testing.T) {
		token, err := jwtService.GenerateAccessToken(user)
		if err != nil {
			t.Fatalf("Expected successful token generation with key rotation, got error: %v", err)
		}

		parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (any, error) {
			return []byte(cfg.KeyRotation.CurrentKey), nil
		})
		if err != nil {
			t.Fatalf("Failed to parse token with key rotation: %v", err)
		}

		if !parsedToken.Valid {
			t.Error("Generated token should be valid")
		}

		if kid, ok := parsedToken.Header["kid"].(string); !ok || kid != cfg.KeyRotation.KeyID {
			t.Errorf("Expected key ID %s, got %v", cfg.KeyRotation.KeyID, parsedToken.Header["kid"])
		}
	})

	t.Run("Verify_Token_With_Current_Key", func(t *testing.T) {
		token, err := jwtService.GenerateAccessToken(user)
		if err != nil {
			t.Fatalf("Failed to generate token: %v", err)
		}

		userID, err := jwtService.VerifyAccessToken(token)
		if err != nil {
			t.Fatalf("Expected successful token verification with current key, got error: %v", err)
		}

		if userID != user.ID {
			t.Errorf("Expected user ID %d, got %d", user.ID, userID)
		}
	})

	t.Run("Legacy_Token_Without_KID_Remains_Valid", func(t *testing.T) {
		legacyCfg := &config.JWTConfig{
			Secret:            "legacy-secret-key-that-is-long-enough-for-security",
			Expiration:        1,
			RefreshExpiration: 24,
			Issuer:            "legacy-issuer",
			Audience:          "legacy-audience",
			KeyRotation:       config.KeyRotationConfig{Enabled: false},
		}

		legacyUserRepo := &jwtMockUserRepo{users: map[uint]*database.User{user.ID: user}}
		legacyRefreshRepo := &jwtMockRefreshTokenRepo{tokens: make(map[string]*database.RefreshToken)}
		legacyService := NewJWTService(legacyCfg, legacyUserRepo, legacyRefreshRepo)

		legacyToken, err := legacyService.GenerateAccessToken(user)
		if err != nil {
			t.Fatalf("Failed to generate legacy token: %v", err)
		}

		legacyCfg.KeyRotation.Enabled = true
		legacyCfg.KeyRotation.CurrentKey = "rotated-current-key-that-is-long-enough-for-security"
		legacyCfg.KeyRotation.PreviousKey = legacyCfg.Secret
		legacyCfg.KeyRotation.KeyID = "rotated-key-id"

		parsedUserID, err := legacyService.VerifyAccessToken(legacyToken)
		if err != nil {
			t.Fatalf("Legacy token should remain valid after enabling rotation: %v", err)
		}
		if parsedUserID != user.ID {
			t.Fatalf("Expected user ID %d, got %d", user.ID, parsedUserID)
		}
	})

	t.Run("Legacy_Token_With_Previous_KID_Remains_Valid", func(t *testing.T) {
		rotCfg := &config.JWTConfig{
			Secret:            "unused-secret-key-that-is-long-enough-for-security",
			Expiration:        1,
			RefreshExpiration: 24,
			Issuer:            "rotation-issuer",
			Audience:          "rotation-audience",
			KeyRotation: config.KeyRotationConfig{
				Enabled:    true,
				CurrentKey: "rotation-key-v1-that-is-long-enough-for-security",
				KeyID:      "key-id-v1",
			},
		}

		rotUserRepo := &jwtMockUserRepo{users: map[uint]*database.User{user.ID: user}}
		rotRefreshRepo := &jwtMockRefreshTokenRepo{tokens: make(map[string]*database.RefreshToken)}
		rotService := NewJWTService(rotCfg, rotUserRepo, rotRefreshRepo)

		tokenV1, err := rotService.GenerateAccessToken(user)
		if err != nil {
			t.Fatalf("Failed to generate v1 token: %v", err)
		}

		rotCfg.KeyRotation.PreviousKey = rotCfg.KeyRotation.CurrentKey
		rotCfg.KeyRotation.CurrentKey = "rotation-key-v2-that-is-long-enough-for-security"
		rotCfg.KeyRotation.KeyID = "key-id-v2"

		parsedUserID, err := rotService.VerifyAccessToken(tokenV1)
		if err != nil {
			t.Fatalf("Token signed with previous key should remain valid: %v", err)
		}
		if parsedUserID != user.ID {
			t.Fatalf("Expected user ID %d, got %d", user.ID, parsedUserID)
		}
	})

	t.Run("Unknown_KID_Is_Rejected", func(t *testing.T) {
		cfg := &config.JWTConfig{
			Secret:            "unused-secret-key-that-is-long-enough-for-security",
			Expiration:        1,
			RefreshExpiration: 24,
			Issuer:            "issuer",
			Audience:          "audience",
			KeyRotation: config.KeyRotationConfig{
				Enabled:    true,
				CurrentKey: "current-key-for-unknown-kid-test-that-is-long-enough",
				KeyID:      "expected-key-id",
			},
		}

		repo := &jwtMockUserRepo{users: map[uint]*database.User{user.ID: user}}
		refreshRepo := &jwtMockRefreshTokenRepo{tokens: make(map[string]*database.RefreshToken)}
		service := NewJWTService(cfg, repo, refreshRepo)

		claims := TokenClaims{
			UserID:         user.ID,
			Username:       user.Username,
			SessionVersion: user.SessionVersion,
			TokenType:      TokenTypeAccess,
			RegisteredClaims: jwt.RegisteredClaims{
				Issuer:    cfg.Issuer,
				Audience:  []string{cfg.Audience},
				Subject:   fmt.Sprint(user.ID),
				IssuedAt:  jwt.NewNumericDate(time.Now()),
				ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
			},
		}

		token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
		token.Header["kid"] = "unexpected-key-id"
		tokenString, err := token.SignedString([]byte(cfg.KeyRotation.CurrentKey))
		if err != nil {
			t.Fatalf("Failed to sign token: %v", err)
		}

		_, err = service.VerifyAccessToken(tokenString)
		if !errors.Is(err, ErrInvalidKeyID) {
			t.Fatalf("Expected ErrInvalidKeyID, got %v", err)
		}
	})
}

func TestJWTService_ErrorHandling(t *testing.T) {
	jwtService, _, _ := createTestJWTService()

	t.Run("Invalid_Issuer", func(t *testing.T) {

		claims := TokenClaims{
			UserID:         1,
			Username:       "testuser",
			SessionVersion: 1,
			TokenType:      TokenTypeAccess,
			RegisteredClaims: jwt.RegisteredClaims{
				Issuer:    "wrong-issuer",
				Audience:  []string{jwtService.config.Audience},
				Subject:   "1",
				IssuedAt:  jwt.NewNumericDate(time.Now()),
				ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
			},
		}

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

		_, err = jwtService.VerifyAccessToken(tokenString)
		if err == nil {
			t.Error("Expected error for invalid issuer")
		}
		if !errors.Is(err, ErrInvalidIssuer) {
			t.Errorf("Expected ErrInvalidIssuer, got %v", err)
		}
	})

	t.Run("Invalid_Audience", func(t *testing.T) {

		claims := TokenClaims{
			UserID:         1,
			Username:       "testuser",
			SessionVersion: 1,
			TokenType:      TokenTypeAccess,
			RegisteredClaims: jwt.RegisteredClaims{
				Issuer:    jwtService.config.Issuer,
				Audience:  []string{"wrong-audience"},
				Subject:   "1",
				IssuedAt:  jwt.NewNumericDate(time.Now()),
				ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
			},
		}

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

		_, err = jwtService.VerifyAccessToken(tokenString)
		if err == nil {
			t.Error("Expected error for invalid audience")
		}
		if !errors.Is(err, ErrInvalidAudience) {
			t.Errorf("Expected ErrInvalidAudience, got %v", err)
		}
	})

	t.Run("Expired_Token", func(t *testing.T) {

		claims := TokenClaims{
			UserID:         1,
			Username:       "testuser",
			SessionVersion: 1,
			TokenType:      TokenTypeAccess,
			RegisteredClaims: jwt.RegisteredClaims{
				Issuer:    jwtService.config.Issuer,
				Audience:  []string{jwtService.config.Audience},
				Subject:   "1",
				IssuedAt:  jwt.NewNumericDate(time.Now().Add(-2 * time.Hour)),
				ExpiresAt: jwt.NewNumericDate(time.Now().Add(-time.Hour)),
			},
		}

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

		_, err = jwtService.VerifyAccessToken(tokenString)
		if err == nil {
			t.Error("Expected error for expired token")
		}
		if !errors.Is(err, ErrTokenExpired) {
			t.Errorf("Expected ErrTokenExpired, got %v", err)
		}
	})

	t.Run("Subject_Mismatch", func(t *testing.T) {
		claims := TokenClaims{
			UserID:         1,
			Username:       "testuser",
			SessionVersion: 1,
			TokenType:      TokenTypeAccess,
			RegisteredClaims: jwt.RegisteredClaims{
				Issuer:    jwtService.config.Issuer,
				Audience:  []string{jwtService.config.Audience},
				Subject:   "999",
				IssuedAt:  jwt.NewNumericDate(time.Now()),
				ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
			},
		}

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

		_, err = jwtService.VerifyAccessToken(tokenString)
		if !errors.Is(err, ErrInvalidToken) {
			t.Fatalf("Expected ErrInvalidToken for subject mismatch, got %v", err)
		}
	})
}

func TestJWTService_HelperFunctions(t *testing.T) {
	jwtService, _, _ := createTestJWTService()

	t.Run("HashToken", func(t *testing.T) {
		token := "test-token"
		hash1 := jwtService.hashToken(token)
		hash2 := jwtService.hashToken(token)

		if hash1 != hash2 {
			t.Error("Hash should be deterministic")
		}

		if hash1 == token {
			t.Error("Hash should be different from original token")
		}

		hash3 := jwtService.hashToken("different-token")
		if hash1 == hash3 {
			t.Error("Different tokens should produce different hashes")
		}
	})

	t.Run("Contains", func(t *testing.T) {
		slice := []string{"item1", "item2", "item3"}

		if !slices.Contains(slice, "item1") {
			t.Error("Should contain item1")
		}

		if !slices.Contains(slice, "item2") {
			t.Error("Should contain item2")
		}

		if slices.Contains(slice, "item4") {
			t.Error("Should not contain item4")
		}

		if slices.Contains(slice, "") {
			t.Error("Should not contain empty string")
		}
	})
}

func TestJWTService_Integration(t *testing.T) {
	jwtService, userRepo, _ := createTestJWTService()
	user := createTestUser()
	userRepo.users[user.ID] = user

	t.Run("Complete_Flow", func(t *testing.T) {

		accessToken, err := jwtService.GenerateAccessToken(user)
		if err != nil {
			t.Fatalf("Failed to generate access token: %v", err)
		}

		refreshToken, err := jwtService.GenerateRefreshToken(user)
		if err != nil {
			t.Fatalf("Failed to generate refresh token: %v", err)
		}

		userID, err := jwtService.VerifyAccessToken(accessToken)
		if err != nil {
			t.Fatalf("Failed to verify access token: %v", err)
		}
		if userID != user.ID {
			t.Errorf("Expected user ID %d, got %d", user.ID, userID)
		}

		newAccessToken, rotatedRefreshToken, err := jwtService.RefreshAccessTokenWithRotation(refreshToken)
		if err != nil {
			t.Fatalf("Failed to refresh access token: %v", err)
		}

		userID, err = jwtService.VerifyAccessToken(newAccessToken)
		if err != nil {
			t.Fatalf("Failed to verify new access token: %v", err)
		}
		if userID != user.ID {
			t.Errorf("Expected user ID %d, got %d", user.ID, userID)
		}

		err = jwtService.RevokeRefreshToken(rotatedRefreshToken)
		if err != nil {
			t.Fatalf("Failed to revoke refresh token: %v", err)
		}

		_, _, err = jwtService.RefreshAccessTokenWithRotation(rotatedRefreshToken)
		if err == nil {
			t.Error("Expected error when using revoked refresh token")
		}
		if !errors.Is(err, ErrRefreshTokenInvalid) {
			t.Errorf("Expected ErrRefreshTokenInvalid, got %v", err)
		}
	})
}
