package traefikoidc

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/suite"
)

// SessionBehaviourSuite tests session management behavior
type SessionBehaviourSuite struct {
	suite.Suite
	logger         *Logger
	sessionManager *SessionManager
}

func (s *SessionBehaviourSuite) SetupTest() {
	s.logger = NewLogger("error")

	var err error
	s.sessionManager, err = NewSessionManager(
		"test-encryption-key-32-bytes-long!!",
		false,
		"",
		"",
		0,
		s.logger,
	)
	s.Require().NoError(err)
}

func (s *SessionBehaviourSuite) TearDownTest() {
	if s.sessionManager != nil {
		s.sessionManager.Shutdown()
	}
}

// TestValidateSessionHealth_NilSession tests validation with nil session
func (s *SessionBehaviourSuite) TestValidateSessionHealth_NilSession() {
	err := s.sessionManager.ValidateSessionHealth(nil)
	s.Error(err)
	s.Contains(err.Error(), "session data is nil")
}

// TestValidateSessionHealth_NotAuthenticated tests validation with unauthenticated session
func (s *SessionBehaviourSuite) TestValidateSessionHealth_NotAuthenticated() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Session is not authenticated by default
	err = s.sessionManager.ValidateSessionHealth(session)
	s.Error(err)
	s.Contains(err.Error(), "session is not authenticated")
}

// TestValidateSessionHealth_AuthenticatedSession tests validation with authenticated session
func (s *SessionBehaviourSuite) TestValidateSessionHealth_AuthenticatedSession() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set session as authenticated
	err = session.SetAuthenticated(true)
	s.Require().NoError(err)

	// Validate health - should pass
	err = s.sessionManager.ValidateSessionHealth(session)
	s.NoError(err)
}

// TestValidateSessionHealth_WithValidAccessToken tests validation with valid access token
func (s *SessionBehaviourSuite) TestValidateSessionHealth_WithValidAccessToken() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set session as authenticated
	err = session.SetAuthenticated(true)
	s.Require().NoError(err)

	// Set a valid-format access token (opaque token format)
	session.SetAccessToken("valid-access-token-with-sufficient-length-for-testing")

	// Validate health - should pass
	err = s.sessionManager.ValidateSessionHealth(session)
	s.NoError(err)
}

// TestValidateSessionHealth_CorruptedAccessToken tests validation with corrupted access token
func (s *SessionBehaviourSuite) TestValidateSessionHealth_CorruptedAccessToken() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set session as authenticated
	err = session.SetAuthenticated(true)
	s.Require().NoError(err)

	// Manually set a corrupted access token
	session.accessSession.Values["token"] = "__CORRUPTION_MARKER_TEST__"
	session.accessSession.Values["compressed"] = false

	// Validate health - should fail
	err = s.sessionManager.ValidateSessionHealth(session)
	s.Error(err)
	s.Contains(err.Error(), "access token validation failed")
}

// TestValidateSessionHealth_PathTraversalAttempt tests detection of path traversal in session
func (s *SessionBehaviourSuite) TestValidateSessionHealth_PathTraversalAttempt() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set session as authenticated
	err = session.SetAuthenticated(true)
	s.Require().NoError(err)

	// Inject path traversal attempt in session value
	session.mainSession.Values["malicious"] = "../../../etc/passwd"

	// Validate health - should detect tampering
	err = s.sessionManager.ValidateSessionHealth(session)
	s.Error(err)
	s.Contains(err.Error(), "tampering detected")
}

// TestValidateSessionHealth_XSSAttempt tests detection of XSS attempt in session
func (s *SessionBehaviourSuite) TestValidateSessionHealth_XSSAttempt() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set session as authenticated
	err = session.SetAuthenticated(true)
	s.Require().NoError(err)

	// Inject XSS attempt in session value
	session.mainSession.Values["xss"] = "<script>alert('xss')</script>"

	// Validate health - should detect tampering
	err = s.sessionManager.ValidateSessionHealth(session)
	s.Error(err)
	s.Contains(err.Error(), "tampering detected")
}

// TestValidateSessionHealth_SuspiciouslyLongValue tests detection of suspiciously long values
func (s *SessionBehaviourSuite) TestValidateSessionHealth_SuspiciouslyLongValue() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set session as authenticated
	err = session.SetAuthenticated(true)
	s.Require().NoError(err)

	// Inject suspiciously long value
	session.mainSession.Values["long_value"] = strings.Repeat("x", 15000)

	// Validate health - should detect suspicious value
	err = s.sessionManager.ValidateSessionHealth(session)
	s.Error(err)
	s.Contains(err.Error(), "suspiciously long")
}

// TestValidateTokenFormat_EmptyToken tests validation of empty token
func (s *SessionBehaviourSuite) TestValidateTokenFormat_EmptyToken() {
	err := s.sessionManager.validateTokenFormat("", "access_token")
	s.NoError(err) // Empty tokens are valid (just not present)
}

// TestValidateTokenFormat_ValidJWT tests validation of valid JWT format
func (s *SessionBehaviourSuite) TestValidateTokenFormat_ValidJWT() {
	// Valid JWT format (header.payload.signature)
	jwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature"
	err := s.sessionManager.validateTokenFormat(jwt, "id_token")
	s.NoError(err)
}

// TestValidateTokenFormat_InvalidJWTWithEmptyPart tests JWT with empty part
func (s *SessionBehaviourSuite) TestValidateTokenFormat_InvalidJWTWithEmptyPart() {
	// JWT with empty part
	invalidJWT := "header..signature"
	err := s.sessionManager.validateTokenFormat(invalidJWT, "id_token")
	s.Error(err)
	s.Contains(err.Error(), "empty part")
}

// TestValidateTokenFormat_CorruptionMarker tests detection of corruption marker
func (s *SessionBehaviourSuite) TestValidateTokenFormat_CorruptionMarker() {
	err := s.sessionManager.validateTokenFormat("__CORRUPTION_MARKER_TEST__", "access_token")
	s.Error(err)
	s.Contains(err.Error(), "corruption marker")
}

// TestPeriodicChunkCleanup tests the periodic cleanup function
func (s *SessionBehaviourSuite) TestPeriodicChunkCleanup() {
	// This should not panic or error
	s.sessionManager.PeriodicChunkCleanup()

	// Verify it can be called multiple times
	s.sessionManager.PeriodicChunkCleanup()
	s.sessionManager.PeriodicChunkCleanup()
}

// TestPeriodicChunkCleanup_WithCanceledContext tests cleanup with canceled context
func (s *SessionBehaviourSuite) TestPeriodicChunkCleanup_WithCanceledContext() {
	// Cancel the context
	s.sessionManager.cancel()

	// Should return early without panicking
	s.sessionManager.PeriodicChunkCleanup()
}

// TestGetSessionStats tests session statistics retrieval
func (s *SessionBehaviourSuite) TestGetSessionStats() {
	stats := s.sessionManager.GetSessionStats()

	s.NotNil(stats)
	s.Contains(stats, "active_sessions")
	s.Contains(stats, "pool_hits")
	s.Contains(stats, "pool_misses")
}

// TestGetSessionMetrics tests session metrics retrieval
func (s *SessionBehaviourSuite) TestGetSessionMetrics() {
	metrics := s.sessionManager.GetSessionMetrics()

	s.NotNil(metrics)
	s.Equal("CookieStore", metrics["session_manager_type"])
	s.Contains(metrics, "force_https")
	s.Contains(metrics, "absolute_timeout_hours")
	s.Contains(metrics, "max_cookie_size")
	s.Contains(metrics, "has_encryption")
}

// TestEnhanceSessionSecurity_NilOptions tests enhancing nil options
func (s *SessionBehaviourSuite) TestEnhanceSessionSecurity_NilOptions() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	options := s.sessionManager.EnhanceSessionSecurity(nil, req)

	s.NotNil(options)
	s.True(options.HttpOnly)
	s.Equal("/", options.Path)
}

// TestEnhanceSessionSecurity_WithHTTPS tests enhancing with HTTPS request
func (s *SessionBehaviourSuite) TestEnhanceSessionSecurity_WithHTTPS() {
	req := httptest.NewRequest(http.MethodGet, "https://example.com/test", nil)
	req.Header.Set("X-Forwarded-Proto", "https")

	options := s.sessionManager.EnhanceSessionSecurity(nil, req)

	s.True(options.Secure)
	s.Equal(http.SameSiteLaxMode, options.SameSite)
}

// TestEnhanceSessionSecurity_MissingUserAgent tests handling of missing User-Agent
func (s *SessionBehaviourSuite) TestEnhanceSessionSecurity_MissingUserAgent() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	// Explicitly remove User-Agent
	req.Header.Del("User-Agent")

	options := s.sessionManager.EnhanceSessionSecurity(nil, req)

	// Should have reduced MaxAge for suspicious requests
	s.NotNil(options)
}

// TestCleanupOldCookies tests cookie cleanup
func (s *SessionBehaviourSuite) TestCleanupOldCookies() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	req.Header.Set("Host", "example.com")
	rw := httptest.NewRecorder()

	// Add some cookies that match the prefix
	req.AddCookie(&http.Cookie{Name: "_oidc_raczylo_m", Value: "test"})
	req.AddCookie(&http.Cookie{Name: "_oidc_raczylo_a", Value: "test"})

	// Should not panic
	s.sessionManager.CleanupOldCookies(rw, req)
}

// TestSessionData_DirtyTracking tests dirty flag tracking
func (s *SessionBehaviourSuite) TestSessionData_DirtyTracking() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Initially not dirty (fresh session from pool)
	s.False(session.IsDirty())

	// Mark dirty
	session.MarkDirty()
	s.True(session.IsDirty())

	// Reset should clear dirty flag
	session.Reset()
	s.False(session.IsDirty())
}

// TestSessionData_SetUserIdentifier tests user identifier setter with dirty tracking
func (s *SessionBehaviourSuite) TestSessionData_SetUserIdentifier() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	session.SetUserIdentifier("test@example.com")
	s.Equal("test@example.com", session.GetUserIdentifier())
	s.True(session.IsDirty())
}

// TestSessionData_SetCSRF tests CSRF setter with dirty tracking
func (s *SessionBehaviourSuite) TestSessionData_SetCSRF() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set CSRF
	session.SetCSRF("csrf-token-value")
	s.Equal("csrf-token-value", session.GetCSRF())
	s.True(session.IsDirty())

	// Setting same value should not trigger dirty again
	session.dirty = false
	session.SetCSRF("csrf-token-value")
	s.False(session.IsDirty())
}

// TestSessionData_SetNonce tests nonce setter with dirty tracking
func (s *SessionBehaviourSuite) TestSessionData_SetNonce() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set nonce
	session.SetNonce("nonce-value")
	s.Equal("nonce-value", session.GetNonce())
	s.True(session.IsDirty())
}

// TestSessionData_SetCodeVerifier tests code verifier setter
func (s *SessionBehaviourSuite) TestSessionData_SetCodeVerifier() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set code verifier
	session.SetCodeVerifier("pkce-code-verifier")
	s.Equal("pkce-code-verifier", session.GetCodeVerifier())
	s.True(session.IsDirty())
}

// TestSessionData_SetIncomingPath tests incoming path setter
func (s *SessionBehaviourSuite) TestSessionData_SetIncomingPath() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set incoming path
	session.SetIncomingPath("/original/path?query=value")
	s.Equal("/original/path?query=value", session.GetIncomingPath())
	s.True(session.IsDirty())
}

// TestSessionData_RedirectCount tests redirect count operations
func (s *SessionBehaviourSuite) TestSessionData_RedirectCount() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Initial count should be 0
	s.Equal(0, session.GetRedirectCount())

	// Increment
	session.IncrementRedirectCount()
	s.Equal(1, session.GetRedirectCount())

	session.IncrementRedirectCount()
	s.Equal(2, session.GetRedirectCount())

	// Reset
	session.ResetRedirectCount()
	s.Equal(0, session.GetRedirectCount())
}

// TestSessionData_SetAccessToken tests access token storage
func (s *SessionBehaviourSuite) TestSessionData_SetAccessToken() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set a valid opaque access token
	token := "opaque-access-token-with-sufficient-length-for-testing"
	session.SetAccessToken(token)

	// Get the token back
	retrieved := session.GetAccessToken()
	s.Equal(token, retrieved)
}

// TestSessionData_SetAccessToken_InvalidFormat tests rejection of invalid token format
func (s *SessionBehaviourSuite) TestSessionData_SetAccessToken_InvalidFormat() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set a token with invalid format (exactly 1 dot is invalid)
	session.SetAccessToken("invalid.token")

	// Should be rejected
	retrieved := session.GetAccessToken()
	s.Empty(retrieved)
}

// TestSessionData_SetAccessToken_TooShortOpaque tests rejection of too short opaque token
func (s *SessionBehaviourSuite) TestSessionData_SetAccessToken_TooShortOpaque() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set a very short opaque token (less than 20 chars)
	session.SetAccessToken("short")

	// Should be rejected
	retrieved := session.GetAccessToken()
	s.Empty(retrieved)
}

// TestSessionData_SetIDToken_ValidJWT tests ID token storage with valid JWT
func (s *SessionBehaviourSuite) TestSessionData_SetIDToken_ValidJWT() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set a valid JWT format ID token
	token := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.signature"
	session.SetIDToken(token)

	// The ID token should be stored - verify it directly from the session
	// since GetIDToken uses ChunkManager which may apply additional validation
	storedToken, _ := session.idTokenSession.Values["token"].(string)
	s.NotEmpty(storedToken)
	s.True(session.IsDirty())
}

// TestSessionData_SetIDToken_InvalidFormat tests rejection of invalid ID token format
func (s *SessionBehaviourSuite) TestSessionData_SetIDToken_InvalidFormat() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set a non-JWT format (ID tokens must be JWT)
	session.SetIDToken("not-a-jwt-token")

	// Should be rejected
	retrieved := session.GetIDToken()
	s.Empty(retrieved)
}

// TestSessionData_SetRefreshToken tests refresh token storage
func (s *SessionBehaviourSuite) TestSessionData_SetRefreshToken() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Set refresh token (opaque format is valid)
	token := "refresh-token-opaque-format-value"
	session.SetRefreshToken(token)

	// Get the token back
	retrieved := session.GetRefreshToken()
	s.Equal(token, retrieved)
}

// TestSessionData_SetRefreshToken_TooLarge tests rejection of too large refresh token
func (s *SessionBehaviourSuite) TestSessionData_SetRefreshToken_TooLarge() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Create a very large token (over 50KB)
	largeToken := strings.Repeat("x", 60*1024)
	session.SetRefreshToken(largeToken)

	// Should be rejected
	retrieved := session.GetRefreshToken()
	s.Empty(retrieved)
}

// TestSessionData_GetRefreshTokenIssuedAt tests refresh token issued timestamp
func (s *SessionBehaviourSuite) TestSessionData_GetRefreshTokenIssuedAt() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Before setting refresh token, issued_at should be zero
	issuedAt := session.GetRefreshTokenIssuedAt()
	s.True(issuedAt.IsZero())

	// Set refresh token (this sets issued_at)
	session.SetRefreshToken("refresh-token-value-here")

	// Now issued_at should be set
	issuedAt = session.GetRefreshTokenIssuedAt()
	s.False(issuedAt.IsZero())
	s.True(time.Since(issuedAt) < 5*time.Second) // Should be very recent
}

// TestSessionData_Clear tests session clearing
func (s *SessionBehaviourSuite) TestSessionData_Clear() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	rw := httptest.NewRecorder()

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)

	// Set some data
	err = session.SetAuthenticated(true)
	s.Require().NoError(err)
	session.SetUserIdentifier("test@example.com")
	session.SetCSRF("csrf-token")

	// Clear session
	err = session.Clear(req, rw)
	s.NoError(err)

	// After clear, session is returned to pool, so we shouldn't use it
}

// TestSessionData_Save tests session saving
func (s *SessionBehaviourSuite) TestSessionData_Save() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	rw := httptest.NewRecorder()

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)
	defer session.returnToPoolSafely()

	// Modify session
	session.SetUserIdentifier("test@example.com")
	s.True(session.IsDirty())

	// Save session
	err = session.Save(req, rw)
	s.NoError(err)

	// After save, dirty flag should be cleared
	s.False(session.IsDirty())

	// Response should have cookies
	cookies := rw.Result().Cookies()
	s.NotEmpty(cookies)
}

// TestSessionData_ReturnToPool tests returning session to pool
func (s *SessionBehaviourSuite) TestSessionData_ReturnToPool() {
	req := httptest.NewRequest(http.MethodGet, "/test", nil)

	session, err := s.sessionManager.GetSession(req)
	s.Require().NoError(err)

	// Initially in use
	s.True(session.inUse)

	// Return to pool safely
	session.returnToPoolSafely()

	// Should no longer be in use
	s.False(session.inUse)
}

// TestTokenCompression tests token compression functionality
func (s *SessionBehaviourSuite) TestTokenCompression() {
	// A typical JWT token that could benefit from compression
	token := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2V5LWlkIn0.eyJpc3MiOiJodHRwczovL3Rlc3QtaXNzdWVyLmNvbSIsInN1YiI6InRlc3Qtc3ViamVjdCIsImF1ZCI6InRlc3QtY2xpZW50LWlkIiwiZXhwIjoxNzAyNDE2MDAwLCJpYXQiOjE3MDI0MTI0MDAsIm5vbmNlIjoidGVzdC1ub25jZSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSJ9.signature_data_here"

	compressed := compressToken(token)

	// Decompress and verify
	decompressed := decompressToken(compressed)
	s.Equal(token, decompressed)
}

// TestTokenCompression_EmptyToken tests compression of empty token
func (s *SessionBehaviourSuite) TestTokenCompression_EmptyToken() {
	compressed := compressToken("")
	s.Empty(compressed)

	decompressed := decompressToken("")
	s.Empty(decompressed)
}

// TestTokenCompression_InvalidFormat tests compression of non-JWT token
func (s *SessionBehaviourSuite) TestTokenCompression_InvalidFormat() {
	// Token without proper JWT format (wrong number of dots)
	token := "not-a-jwt"
	compressed := compressToken(token)

	// Should return original (not compressed)
	s.Equal(token, compressed)
}

// TestSplitIntoChunks tests chunk splitting functionality
func (s *SessionBehaviourSuite) TestSplitIntoChunks() {
	// Test with a string that needs splitting
	data := strings.Repeat("x", 3000)
	chunks := splitIntoChunks(data, 1000)

	s.Equal(3, len(chunks))
	s.Equal(1000, len(chunks[0]))
	s.Equal(1000, len(chunks[1]))
	s.Equal(1000, len(chunks[2]))

	// Verify reassembly
	reassembled := strings.Join(chunks, "")
	s.Equal(data, reassembled)
}

// TestSplitIntoChunks_SmallData tests chunk splitting with data smaller than chunk size
func (s *SessionBehaviourSuite) TestSplitIntoChunks_SmallData() {
	data := "small"
	chunks := splitIntoChunks(data, 1000)

	s.Equal(1, len(chunks))
	s.Equal(data, chunks[0])
}

// TestValidateChunkSize tests chunk size validation
func (s *SessionBehaviourSuite) TestValidateChunkSize() {
	// Small chunk should be valid
	s.True(validateChunkSize("small_chunk_data"))

	// Very large chunk should be invalid
	largeChunk := strings.Repeat("x", 5000)
	s.False(validateChunkSize(largeChunk))
}

// TestIsCorruptionMarker tests corruption marker detection
func (s *SessionBehaviourSuite) TestIsCorruptionMarker() {
	// Known corruption markers
	s.True(isCorruptionMarker("__CORRUPTION_MARKER_TEST__"))
	s.True(isCorruptionMarker("__INVALID_BASE64_DATA__"))
	s.True(isCorruptionMarker("<<<CORRUPTED>>>"))

	// Normal data
	s.False(isCorruptionMarker("normal-data"))
	s.False(isCorruptionMarker("eyJhbGciOiJSUzI1NiJ9"))
	s.False(isCorruptionMarker(""))

	// Data with special characters (in long strings)
	s.True(isCorruptionMarker("long-string-with!special@chars"))
}

// TestSessionManager_Shutdown tests graceful shutdown
func (s *SessionBehaviourSuite) TestSessionManager_Shutdown() {
	// Create a new session manager for this test
	sm, err := NewSessionManager(
		"test-encryption-key-32-bytes-long!!",
		false,
		"",
		"",
		0,
		s.logger,
	)
	s.Require().NoError(err)

	// Shutdown should complete without error
	err = sm.Shutdown()
	s.NoError(err)

	// Second shutdown should also be safe (idempotent)
	err = sm.Shutdown()
	s.NoError(err)
}

// TestCookieNameHelpers tests cookie name helper methods
func (s *SessionBehaviourSuite) TestCookieNameHelpers() {
	s.Equal("_oidc_raczylo_m", s.sessionManager.mainCookieName())
	s.Equal("_oidc_raczylo_a", s.sessionManager.accessTokenCookieName())
	s.Equal("_oidc_raczylo_r", s.sessionManager.refreshTokenCookieName())
	s.Equal("_oidc_raczylo_id", s.sessionManager.idTokenCookieName())
}

// TestSessionManager_CustomCookiePrefix tests custom cookie prefix
func (s *SessionBehaviourSuite) TestSessionManager_CustomCookiePrefix() {
	customSM, err := NewSessionManager(
		"test-encryption-key-32-bytes-long!!",
		false,
		"",
		"custom_prefix_",
		0,
		s.logger,
	)
	s.Require().NoError(err)
	defer customSM.Shutdown()

	s.Equal("custom_prefix_m", customSM.mainCookieName())
	s.Equal("custom_prefix_a", customSM.accessTokenCookieName())
	s.Equal("custom_prefix_r", customSM.refreshTokenCookieName())
	s.Equal("custom_prefix_id", customSM.idTokenCookieName())
}

// TestSessionManager_ShortEncryptionKey tests rejection of short encryption key
func (s *SessionBehaviourSuite) TestSessionManager_ShortEncryptionKey() {
	_, err := NewSessionManager(
		"short", // Too short
		false,
		"",
		"",
		0,
		s.logger,
	)
	s.Error(err)
	s.Contains(err.Error(), "encryption key must be at least")
}

// TestGenerateSecureRandomString tests secure random string generation
func (s *SessionBehaviourSuite) TestGenerateSecureRandomString() {
	// Generate two random strings
	str1, err := generateSecureRandomString(32)
	s.NoError(err)
	s.Equal(64, len(str1)) // Hex encoding doubles length

	str2, err := generateSecureRandomString(32)
	s.NoError(err)
	s.Equal(64, len(str2))

	// They should be different
	s.NotEqual(str1, str2)
}

// TestConstantTimeStringCompare tests constant-time string comparison
func (s *SessionBehaviourSuite) TestConstantTimeStringCompare() {
	s.True(constantTimeStringCompare("hello", "hello"))
	s.False(constantTimeStringCompare("hello", "world"))
	s.False(constantTimeStringCompare("hello", "hell"))
	s.False(constantTimeStringCompare("", "hello"))
	s.True(constantTimeStringCompare("", ""))
}

func TestSessionBehaviourSuite(t *testing.T) {
	suite.Run(t, new(SessionBehaviourSuite))
}
