package auth

import (
	"context"
	"net/http"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	pkgauth "github.com/nexuscrm/kernel/pkg/auth"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestValidateSessionClassifiesMalformedBearerAsUnauthorized(t *testing.T) {
	svc := NewAuthService(&userRepoStub{}, &sessionRepoStub{}, nil)
	claims, err := svc.ValidateSession(context.Background(), "not-a-jwt")
	if claims != nil || err == nil {
		t.Fatalf("ValidateSession = %#v, %v", claims, err)
	}
	statusError, ok := err.(interface{ HTTPStatus() int })
	if !ok || statusError.HTTPStatus() != http.StatusUnauthorized {
		t.Fatalf("malformed bearer classification = %T %v", err, err)
	}
	if strings.Contains(err.Error(), "not-a-jwt") {
		t.Fatalf("malformed bearer leaked through error: %v", err)
	}
}

func TestValidateSessionReconstructsDurableSecurityProjection(t *testing.T) {
	token, tokenClaims, err := pkgauth.GenerateToken(pkgauth.UserSession{
		ID: "jwt-user", Scope: "stale-scope", ConnectedAppId: "stale-app",
		GrantType: "stale-grant", SessionClass: "stale-class",
	})
	if err != nil {
		t.Fatalf("GenerateToken: %v", err)
	}
	durableScope := "durable-scope"
	durableApp := "durable-app"
	durableGrant := "durable-grant"
	sessions := &sessionRepoStub{loaded: &models.SystemSession{
		ID: tokenClaims.ID, UserID: "durable-user", Token: token, ExpiresAt: time.Now().Add(time.Hour), Scope: &durableScope,
		ConnectedAppID: &durableApp, GrantType: &durableGrant,
	}}
	svc := NewAuthService(&userRepoStub{user: &models.User{ID: "durable-user", IsActive: true}}, sessions, nil)

	claims, err := svc.ValidateSession(context.Background(), token)
	if err != nil {
		t.Fatalf("ValidateSession: %v", err)
	}
	if claims.User.ID != "durable-user" || claims.Subject != "durable-user" {
		t.Fatalf("principal projection = User.ID %q Subject %q, want durable-user", claims.User.ID, claims.Subject)
	}
	if claims.User.Scope != durableScope || claims.User.ConnectedAppId != durableApp || claims.User.GrantType != durableGrant {
		t.Fatalf("security projection = %#v, want durable session values", claims.User)
	}
	if claims.User.SessionClass != "stale-class" {
		t.Fatalf("SessionClass = %q, want existing JWT session-bound value", claims.User.SessionClass)
	}
}

func TestValidateSessionNilAndEmptyDurableValuesInvalidateConstrainedToken(t *testing.T) {
	for _, representation := range []struct {
		name  string
		value *string
	}{
		{name: "nil", value: nil},
		{name: "empty", value: func() *string { value := ""; return &value }()},
	} {
		for _, authority := range []string{"scope", "connected app", "grant type"} {
			t.Run(representation.name+" "+authority, func(t *testing.T) {
				token, tokenClaims, err := pkgauth.GenerateToken(pkgauth.UserSession{
					ID: "user-1", Scope: "stale-scope", ConnectedAppId: "stale-app",
					GrantType: "stale-grant", SessionClass: "stale-class",
				})
				if err != nil {
					t.Fatalf("GenerateToken: %v", err)
				}
				durableScope, durableApp, durableGrant := "stale-scope", "stale-app", "stale-grant"
				session := &models.SystemSession{
					ID: tokenClaims.ID, UserID: "user-1", Token: token, ExpiresAt: time.Now().Add(time.Hour),
					Scope: &durableScope, ConnectedAppID: &durableApp, GrantType: &durableGrant,
				}
				switch authority {
				case "scope":
					session.Scope = representation.value
				case "connected app":
					session.ConnectedAppID = representation.value
				case "grant type":
					session.GrantType = representation.value
				}
				sessions := &sessionRepoStub{loaded: session}
				svc := NewAuthService(&userRepoStub{user: &models.User{ID: "user-1", IsActive: true}}, sessions, nil)

				claims, err := svc.ValidateSession(context.Background(), token)
				if err == nil || claims != nil {
					t.Fatalf("ValidateSession = %#v, %v; constrained token survived durable %s %s authority loss", claims, err, representation.name, authority)
				}
			})
		}
	}
}

func TestValidateSessionAllowsLegacySessionWhoseIssuedAndDurableGrantsAreEmpty(t *testing.T) {
	token, tokenClaims, err := pkgauth.GenerateToken(pkgauth.UserSession{ID: "user-1"})
	if err != nil {
		t.Fatalf("GenerateToken: %v", err)
	}
	sessions := &sessionRepoStub{loaded: &models.SystemSession{
		ID: tokenClaims.ID, UserID: "user-1", Token: token, ExpiresAt: time.Now().Add(time.Hour),
	}}
	svc := NewAuthService(&userRepoStub{user: &models.User{ID: "user-1", IsActive: true}}, sessions, nil)

	claims, err := svc.ValidateSession(context.Background(), token)
	if err != nil {
		t.Fatalf("ValidateSession: %v", err)
	}
	if claims.User.Scope != "" || claims.User.ConnectedAppId != "" || claims.User.GrantType != "" {
		t.Fatalf("legacy empty projection = %#v", claims.User)
	}
}

func TestValidateSessionRejectsExpiredDurableSessionWhileJWTIsValid(t *testing.T) {
	token, tokenClaims, err := pkgauth.GenerateToken(pkgauth.UserSession{ID: "user-1"})
	if err != nil {
		t.Fatalf("GenerateToken: %v", err)
	}
	sessions := &sessionRepoStub{loaded: &models.SystemSession{
		ID: tokenClaims.ID, UserID: "user-1", Token: token, ExpiresAt: time.Now().Add(-time.Second),
	}}
	svc := NewAuthService(&userRepoStub{user: &models.User{ID: "user-1", IsActive: true}}, sessions, nil)

	if claims, err := svc.ValidateSession(context.Background(), token); err == nil || claims != nil {
		t.Fatalf("ValidateSession = %#v, %v; want durable expiry rejection", claims, err)
	}
}

func TestLoginPersistsControlVerifierOnlyForExplicitInteractiveOptIn(t *testing.T) {
	const password = "correct horse battery staple"
	passwordHash, err := pkgauth.HashPassword(password)
	if err != nil {
		t.Fatalf("HashPassword: %v", err)
	}
	newService := func() (*AuthService, *sessionRepoStub) {
		sessions := &sessionRepoStub{}
		users := &userRepoStub{
			user: &models.User{
				ID: "user-1", Username: "user@example.test", Email: "user@example.test",
				ProfileID: "profile-1", IsActive: true,
			},
			passwordHash: passwordHash,
		}
		return NewAuthService(users, sessions, nil), sessions
	}

	t.Run("explicit platform browser opt-in", func(t *testing.T) {
		svc, sessions := newService()
		result, err := svc.Login(context.Background(), "user@example.test", password, "127.0.0.1", "browser", &SessionOptions{
			SessionClass: OAuthSessionClassPlatformBrowser, IssueControlProof: true,
		})
		if err != nil {
			t.Fatalf("Login: %v", err)
		}
		if result.ControlProof == "" || sessions.inserted == nil || sessions.inserted.ControlProofHash == nil {
			t.Fatalf("controlled login result/session = %#v / %#v", result, sessions.inserted)
		}
		if got, ok := matchingSessionControlProofHash(result.ControlProof, sessions.inserted.ControlProofHash); !ok || got != *sessions.inserted.ControlProofHash {
			t.Fatalf("persisted verifier did not match delivered proof: %q, %v", got, ok)
		}
		if strings.Contains(result.Token, result.ControlProof) {
			t.Fatal("raw control proof leaked into bearer token")
		}
	})

	t.Run("platform browser without transport opt-in remains legacy null", func(t *testing.T) {
		svc, sessions := newService()
		result, err := svc.Login(context.Background(), "user@example.test", password, "127.0.0.1", "browser", &SessionOptions{
			SessionClass: OAuthSessionClassPlatformBrowser,
		})
		if err != nil {
			t.Fatalf("Login: %v", err)
		}
		if result.ControlProof != "" || sessions.inserted == nil || sessions.inserted.ControlProofHash != nil {
			t.Fatalf("transport-inactive login stored control authority: result=%#v session=%#v", result, sessions.inserted)
		}
	})

	t.Run("noninteractive opt-in fails closed", func(t *testing.T) {
		svc, sessions := newService()
		result, err := svc.Login(context.Background(), "user@example.test", password, "127.0.0.1", "api", &SessionOptions{
			ConnectedAppId: "app-1", Scope: "api", GrantType: "authorization_code", IssueControlProof: true,
		})
		if err == nil || result != nil || sessions.inserted != nil {
			t.Fatalf("noninteractive controlled Login = %#v, %v, inserted %#v; want fail closed", result, err, sessions.inserted)
		}
	})
}

func TestControlledSessionCreationCannotDiscardRawProofThroughLegacyOAuthAPI(t *testing.T) {
	users := &userRepoStub{user: &models.User{
		ID: "user-1", Username: "user@example.test", Email: "user@example.test",
		ProfileID: "profile-1", IsActive: true,
	}}
	sessions := &sessionRepoStub{}
	svc := NewAuthService(users, sessions, nil)

	legacy, err := svc.CreateSessionForUser(context.Background(), "user-1", "127.0.0.1", "api", &SessionOptions{IssueControlProof: true})
	if err == nil || legacy != nil || sessions.inserted != nil {
		t.Fatalf("legacy CreateSessionForUser = %#v, %v, inserted %#v; raw proof could be discarded", legacy, err, sessions.inserted)
	}

	controlled, err := svc.CreateControlledSessionForUser(context.Background(), "user-1", "127.0.0.1", "browser", nil)
	if err != nil {
		t.Fatalf("CreateControlledSessionForUser: %v", err)
	}
	if controlled == nil || controlled.SessionToken == nil || controlled.ExpiresAt.IsZero() || controlled.ControlProof == "" || sessions.inserted == nil || sessions.inserted.ControlProofHash == nil {
		t.Fatalf("controlled session result/session = %#v / %#v", controlled, sessions.inserted)
	}
	if _, ok := matchingSessionControlProofHash(controlled.ControlProof, sessions.inserted.ControlProofHash); !ok {
		t.Fatal("controlled session did not atomically return the proof matching its durable verifier")
	}
}

func TestValidateSessionWithRenewalExtendsInteractiveSession(t *testing.T) {
	issuedAt := time.Now().Add(-23 * time.Hour)
	token, tokenClaims, err := pkgauth.GenerateTokenForSession(pkgauth.UserSession{
		ID: "user-1", ConnectedAppId: "nexuscrm-ui", GrantType: OAuthGrantPassword, SessionClass: OAuthSessionClassPlatformBrowser,
	}, "session-1", issuedAt, pkgauth.DefaultSessionLifetime)
	if err != nil {
		t.Fatalf("GenerateToken: %v", err)
	}
	appID := "nexuscrm-ui"
	grant := OAuthGrantPassword
	sessions := &sessionRepoStub{loaded: &models.SystemSession{
		ID: tokenClaims.ID, UserID: "user-1", Token: token, ExpiresAt: time.Now().Add(10 * time.Minute),
		ConnectedAppID: &appID, GrantType: &grant,
	}}
	svc := NewAuthService(&userRepoStub{user: &models.User{ID: "user-1", IsActive: true}}, sessions, nil)
	oldExpiresAt := sessions.loaded.ExpiresAt

	claims, refreshed, err := svc.ValidateSessionWithRenewal(context.Background(), token)
	if err != nil {
		t.Fatalf("ValidateSessionWithRenewal: %v", err)
	}
	if claims == nil || claims.ID != tokenClaims.ID {
		t.Fatalf("claims = %#v, want same session id %q", claims, tokenClaims.ID)
	}
	if refreshed == "" || refreshed == token {
		t.Fatalf("refreshed token = %q, want replacement token", refreshed)
	}
	if sessions.renewedSessionID != tokenClaims.ID || sessions.renewedToken != refreshed {
		t.Fatalf("renewal write = session %q token %q", sessions.renewedSessionID, sessions.renewedToken)
	}
	if sessions.renewedPreviousToken != token {
		t.Fatalf("renewal previous token = %q, want presented token", sessions.renewedPreviousToken)
	}
	if !sessions.renewedExpiresAt.After(oldExpiresAt) {
		t.Fatalf("renewed expiry = %v, want after old expiry %v", sessions.renewedExpiresAt, oldExpiresAt)
	}
	refreshedClaims, err := pkgauth.ValidateToken(refreshed)
	if err != nil {
		t.Fatalf("ValidateToken(refreshed): %v", err)
	}
	if refreshedClaims.ID != tokenClaims.ID {
		t.Fatalf("refreshed session id = %q, want %q", refreshedClaims.ID, tokenClaims.ID)
	}
}

func TestValidateSessionWithRenewalRecoversReplacementDuringSupersededBearerOverlap(t *testing.T) {
	issuedAt := time.Now().Add(-23 * time.Hour)
	oldToken, tokenClaims, err := pkgauth.GenerateTokenForSession(pkgauth.UserSession{
		ID: "user-1", SessionClass: OAuthSessionClassPlatformBrowser,
	}, "session-1", issuedAt, pkgauth.DefaultSessionLifetime)
	if err != nil {
		t.Fatalf("GenerateToken: %v", err)
	}
	sessions := &sessionRepoStub{loaded: &models.SystemSession{
		ID: tokenClaims.ID, UserID: "user-1", Token: oldToken, ExpiresAt: time.Now().Add(10 * time.Minute),
	}}
	svc := NewAuthService(&userRepoStub{user: &models.User{ID: "user-1", IsActive: true}}, sessions, nil)

	_, refreshed, err := svc.ValidateSessionWithRenewal(context.Background(), oldToken)
	if err != nil {
		t.Fatalf("first ValidateSessionWithRenewal: %v", err)
	}
	if refreshed == "" || refreshed == oldToken {
		t.Fatalf("refreshed token = %q, want replacement", refreshed)
	}
	firstRenewedToken := sessions.renewedToken
	currentClaims, err := svc.ValidateSession(context.Background(), firstRenewedToken)
	if err != nil || currentClaims == nil || currentClaims.ID != tokenClaims.ID {
		t.Fatalf("strict current bearer ValidateSession = %#v, %v; want session %q", currentClaims, err, tokenClaims.ID)
	}

	if claims, err := svc.ValidateSession(context.Background(), oldToken); err == nil || claims != nil {
		t.Fatalf("strict old bearer ValidateSession during overlap = %#v, %v; want rejection", claims, err)
	}

	claims, secondRefresh, err := svc.ValidateSessionWithRenewal(context.Background(), oldToken)
	if err != nil {
		t.Fatalf("old bearer ValidateSessionWithRenewal during overlap: %v", err)
	}
	if claims == nil || claims.ID != tokenClaims.ID {
		t.Fatalf("renewal-aware old bearer claims during overlap = %#v, want session %q", claims, tokenClaims.ID)
	}
	if secondRefresh != firstRenewedToken {
		t.Fatalf("old bearer second refresh = %q, want existing durable replacement %q", secondRefresh, firstRenewedToken)
	}
	if sessions.renewedToken != firstRenewedToken {
		t.Fatalf("superseded bearer changed durable token from %q to %q", firstRenewedToken, sessions.renewedToken)
	}
}

func TestValidateSessionWithRenewalRecoversConcurrentCASWinner(t *testing.T) {
	issuedAt := time.Now().Add(-23 * time.Hour)
	oldToken, tokenClaims, err := pkgauth.GenerateTokenForSession(pkgauth.UserSession{
		ID: "user-1", SessionClass: OAuthSessionClassPlatformBrowser,
	}, "session-1", issuedAt, pkgauth.DefaultSessionLifetime)
	if err != nil {
		t.Fatalf("GenerateToken(old): %v", err)
	}
	winnerIssuedAt := time.Now().Add(-renewedBearerOverlapWindow / 2)
	winnerToken, winnerClaims, err := pkgauth.GenerateTokenForSession(pkgauth.UserSession{
		ID: "user-1", SessionClass: OAuthSessionClassPlatformBrowser,
	}, tokenClaims.ID, winnerIssuedAt, pkgauth.DefaultSessionLifetime)
	if err != nil {
		t.Fatalf("GenerateToken(winner): %v", err)
	}
	losesCAS := false
	sessions := &sessionRepoStub{
		loaded: &models.SystemSession{
			ID: tokenClaims.ID, UserID: "user-1", Token: oldToken, ExpiresAt: time.Now().Add(10 * time.Minute),
		},
		renewedResult:      &losesCAS,
		competingToken:     winnerToken,
		competingExpiresAt: winnerClaims.ExpiresAt.Time,
	}
	svc := NewAuthService(&userRepoStub{user: &models.User{ID: "user-1", IsActive: true}}, sessions, nil)

	_, refreshed, err := svc.ValidateSessionWithRenewal(context.Background(), oldToken)
	if err != nil {
		t.Fatalf("ValidateSessionWithRenewal: %v", err)
	}
	if refreshed != winnerToken {
		t.Fatalf("CAS loser refresh = %q, want concurrent winner %q", refreshed, winnerToken)
	}
	if winnerToken == sessions.renewedToken {
		t.Fatal("test setup generated the same token for the CAS winner and losing candidate")
	}
	if sessions.loaded.Token != winnerToken {
		t.Fatalf("durable token = %q, want concurrent winner %q", sessions.loaded.Token, winnerToken)
	}
}

func TestValidateSessionWithRenewalRejectsSupersededBearerAfterOverlap(t *testing.T) {
	issuedAt := time.Now().Add(-23 * time.Hour)
	oldToken, tokenClaims, err := pkgauth.GenerateTokenForSession(pkgauth.UserSession{
		ID: "user-1", SessionClass: OAuthSessionClassPlatformBrowser,
	}, "session-1", issuedAt, pkgauth.DefaultSessionLifetime)
	if err != nil {
		t.Fatalf("GenerateToken: %v", err)
	}
	sessions := &sessionRepoStub{loaded: &models.SystemSession{
		ID: tokenClaims.ID, UserID: "user-1", Token: oldToken, ExpiresAt: time.Now().Add(10 * time.Minute),
	}}
	svc := NewAuthService(&userRepoStub{user: &models.User{ID: "user-1", IsActive: true}}, sessions, nil)

	_, refreshed, err := svc.ValidateSessionWithRenewal(context.Background(), oldToken)
	if err != nil {
		t.Fatalf("ValidateSessionWithRenewal: %v", err)
	}
	if refreshed == "" || refreshed == oldToken {
		t.Fatalf("refreshed token = %q, want replacement", refreshed)
	}
	sessions.loaded.LastActivity = time.Now()
	sessions.loaded.ExpiresAt = time.Now().Add(pkgauth.DefaultSessionLifetime - renewedBearerOverlapWindow - time.Second)

	claims, refreshedAgain, err := svc.ValidateSessionWithRenewal(context.Background(), oldToken)
	if err == nil || claims != nil || refreshedAgain != "" {
		t.Fatalf("old bearer ValidateSessionWithRenewal after overlap = %#v, %q, %v; want superseded rejection", claims, refreshedAgain, err)
	}
}

func TestLogoutCannotRevokeRenewedSessionWithSupersededBearer(t *testing.T) {
	issuedAt := time.Now().Add(-23 * time.Hour)
	oldToken, tokenClaims, err := pkgauth.GenerateTokenForSession(pkgauth.UserSession{
		ID: "user-1", SessionClass: OAuthSessionClassPlatformBrowser,
	}, "session-1", issuedAt, pkgauth.DefaultSessionLifetime)
	if err != nil {
		t.Fatalf("GenerateToken: %v", err)
	}
	sessions := &sessionRepoStub{loaded: &models.SystemSession{
		ID: tokenClaims.ID, UserID: "user-1", Token: oldToken, ExpiresAt: time.Now().Add(10 * time.Minute),
	}}
	svc := NewAuthService(&userRepoStub{user: &models.User{ID: "user-1", IsActive: true}}, sessions, nil)

	_, refreshed, err := svc.ValidateSessionWithRenewal(context.Background(), oldToken)
	if err != nil {
		t.Fatalf("ValidateSessionWithRenewal: %v", err)
	}
	if refreshed == "" || refreshed == oldToken {
		t.Fatalf("refreshed token = %q, want replacement", refreshed)
	}
	sessions.loaded.ExpiresAt = time.Now().Add(pkgauth.DefaultSessionLifetime - renewedBearerOverlapWindow - time.Second)
	if claims, refreshedAgain, err := svc.ValidateSessionWithRenewal(context.Background(), oldToken); err == nil || claims != nil || refreshedAgain != "" {
		t.Fatalf("old bearer ValidateSessionWithRenewal after overlap = %#v, %q, %v; want rejection", claims, refreshedAgain, err)
	}

	if err := svc.Logout(context.Background(), oldToken); err != nil {
		t.Fatalf("Logout(old): %v", err)
	}
	if sessions.revokedSessionID != "" || sessions.loaded.IsRevoked {
		t.Fatalf("superseded bearer revoked current session %q", sessions.revokedSessionID)
	}
	if claims, err := svc.ValidateSession(context.Background(), refreshed); err != nil || claims == nil {
		t.Fatalf("replacement after stale logout = %#v, %v; want active", claims, err)
	}

	if err := svc.Logout(context.Background(), refreshed); err != nil {
		t.Fatalf("Logout(refreshed): %v", err)
	}
	if sessions.revokedSessionID != tokenClaims.ID || !sessions.loaded.IsRevoked {
		t.Fatalf("current bearer revoke = session %q revoked %v, want %q true", sessions.revokedSessionID, sessions.loaded.IsRevoked, tokenClaims.ID)
	}
}

func TestValidateSessionWithRenewalSkipsConnectedAppSession(t *testing.T) {
	token, tokenClaims, err := pkgauth.GenerateToken(pkgauth.UserSession{
		ID: "user-1", ConnectedAppId: "app-1", Scope: "api", GrantType: "authorization_code",
	})
	if err != nil {
		t.Fatalf("GenerateToken: %v", err)
	}
	scope := "api"
	appID := "app-1"
	grant := "authorization_code"
	sessions := &sessionRepoStub{loaded: &models.SystemSession{
		ID: tokenClaims.ID, UserID: "user-1", Token: token, ExpiresAt: time.Now().Add(10 * time.Minute),
		Scope: &scope, ConnectedAppID: &appID, GrantType: &grant,
	}}
	svc := NewAuthService(&userRepoStub{user: &models.User{ID: "user-1", IsActive: true}}, sessions, nil)

	_, refreshed, err := svc.ValidateSessionWithRenewal(context.Background(), token)
	if err != nil {
		t.Fatalf("ValidateSessionWithRenewal: %v", err)
	}
	if refreshed != "" {
		t.Fatalf("refreshed token = %q, want empty for OAuth session", refreshed)
	}
	if sessions.renewedSessionID != "" {
		t.Fatalf("renewal unexpectedly wrote session %q", sessions.renewedSessionID)
	}
}

func TestValidateSessionWithControlRequiresMatchingProofForRenewal(t *testing.T) {
	svc, sessions, token, proof := newControlledBrowserSessionFixture(t)
	otherProof, _, err := newSessionControlProof()
	if err != nil {
		t.Fatal(err)
	}

	for _, test := range []struct {
		name  string
		proof string
	}{
		{name: "missing proof", proof: ""},
		{name: "mismatched proof", proof: otherProof},
	} {
		t.Run(test.name, func(t *testing.T) {
			claims, replacement, proofExpiresAt, err := svc.ValidateSessionWithControl(context.Background(), token, test.proof)
			if err != nil || claims == nil || replacement != "" || !proofExpiresAt.IsZero() {
				t.Fatalf("ValidateSessionWithControl = %#v, %q, %v, %v; want strict current bearer without renewal", claims, replacement, proofExpiresAt, err)
			}
			if sessions.controlRenewedSessionID != "" {
				t.Fatalf("proofless validation attempted controlled renewal for %q", sessions.controlRenewedSessionID)
			}
		})
	}

	oldExpiry := sessions.loaded.ExpiresAt
	claims, replacement, proofExpiresAt, err := svc.ValidateSessionWithControl(context.Background(), token, proof)
	if err != nil || claims == nil {
		t.Fatalf("ValidateSessionWithControl(valid proof): %#v, %v", claims, err)
	}
	if replacement == "" || replacement == token || !proofExpiresAt.Equal(sessions.loaded.ExpiresAt) || !proofExpiresAt.After(oldExpiry) {
		t.Fatalf("controlled replacement = %q expiry %v durable %v old %v", replacement, proofExpiresAt, sessions.loaded.ExpiresAt, oldExpiry)
	}
	if sessions.controlRenewedSessionID != sessions.loaded.ID || sessions.controlRenewPreviousToken != token ||
		sessions.controlRenewProofHash == "" || sessions.controlRenewedToken != replacement {
		t.Fatalf("controlled renewal write = session %q previous %q proof %q replacement %q",
			sessions.controlRenewedSessionID, sessions.controlRenewPreviousToken, sessions.controlRenewProofHash, sessions.controlRenewedToken)
	}
}

func TestValidateInteractiveSessionControlRequiresBoundProof(t *testing.T) {
	svc, sessions, token, proof := newControlledBrowserSessionFixture(t)
	if err := svc.ValidateInteractiveSessionControl(context.Background(), token, proof); err != nil {
		t.Fatalf("ValidateInteractiveSessionControl valid proof: %v", err)
	}
	if err := svc.ValidateInteractiveSessionControl(context.Background(), token, "wrong-proof"); err == nil {
		t.Fatal("ValidateInteractiveSessionControl accepted a mismatched proof")
	}
	sessions.loaded.ControlProofHash = nil
	if err := svc.ValidateInteractiveSessionControl(context.Background(), token, proof); err == nil {
		t.Fatal("ValidateInteractiveSessionControl accepted a session without a durable control proof")
	}
}

func TestValidateSessionWithRenewalCannotDowngradeControlledBrowserSession(t *testing.T) {
	svc, sessions, oldToken, proof := newControlledBrowserSessionFixture(t)

	claims, replacement, err := svc.ValidateSessionWithRenewal(context.Background(), oldToken)
	if err != nil || claims == nil || replacement != "" {
		t.Fatalf("legacy renewal on current controlled bearer = %#v, %q, %v; want strict validation without replacement", claims, replacement, err)
	}
	if sessions.renewedSessionID != "" || sessions.controlRenewedSessionID != "" {
		t.Fatalf("legacy renewal reached a write for controlled row: legacy=%q controlled=%q", sessions.renewedSessionID, sessions.controlRenewedSessionID)
	}

	_, controlledReplacement, _, err := svc.ValidateSessionWithControl(context.Background(), oldToken, proof)
	if err != nil || controlledReplacement == "" {
		t.Fatalf("proof-aware renewal = %q, %v; want replacement", controlledReplacement, err)
	}

	claims, replacement, err = svc.ValidateSessionWithRenewal(context.Background(), oldToken)
	if err == nil || claims != nil || replacement != "" {
		t.Fatalf("legacy recovery of superseded controlled bearer = %#v, %q, %v; want rejection", claims, replacement, err)
	}
}

func TestValidateSessionWithControlRecoversLostResponseButBearerAloneCannot(t *testing.T) {
	svc, sessions, oldToken, proof := newControlledBrowserSessionFixture(t)
	_, replacement, replacementExpiry, err := svc.ValidateSessionWithControl(context.Background(), oldToken, proof)
	if err != nil || replacement == "" {
		t.Fatalf("initial controlled renewal = %q, %v, %v", replacement, replacementExpiry, err)
	}
	otherProof, _, err := newSessionControlProof()
	if err != nil {
		t.Fatal(err)
	}

	for _, test := range []struct {
		name  string
		proof string
	}{
		{name: "stolen superseded bearer alone", proof: ""},
		{name: "superseded bearer with another session proof", proof: otherProof},
	} {
		t.Run(test.name, func(t *testing.T) {
			claims, got, proofExpiresAt, err := svc.ValidateSessionWithControl(context.Background(), oldToken, test.proof)
			if err == nil || claims != nil || got != "" || !proofExpiresAt.IsZero() {
				t.Fatalf("ValidateSessionWithControl = %#v, %q, %v, %v; want superseded rejection", claims, got, proofExpiresAt, err)
			}
		})
	}

	claims, recovered, recoveredExpiry, err := svc.ValidateSessionWithControl(context.Background(), oldToken, proof)
	if err != nil || claims == nil || recovered != replacement || !recoveredExpiry.Equal(replacementExpiry) {
		t.Fatalf("lost-response recovery = %#v, %q, %v, %v; want %q at %v", claims, recovered, recoveredExpiry, err, replacement, replacementExpiry)
	}

	if err := svc.BrowserLogout(context.Background(), oldToken, ""); err != nil {
		t.Fatalf("BrowserLogout(missing proof): %v", err)
	}
	if err := svc.BrowserLogout(context.Background(), oldToken, otherProof); err != nil {
		t.Fatalf("BrowserLogout(wrong proof): %v", err)
	}
	if sessions.loaded.IsRevoked {
		t.Fatal("stolen superseded bearer without matching proof revoked the session")
	}
	if err := svc.Logout(context.Background(), oldToken); err != nil {
		t.Fatalf("RFC7009 Logout(old): %v", err)
	}
	if sessions.loaded.IsRevoked {
		t.Fatal("token-bound RFC7009 logout changed behavior for a superseded bearer")
	}
	if err := svc.BrowserLogout(context.Background(), oldToken, proof); err != nil {
		t.Fatalf("BrowserLogout(valid proof): %v", err)
	}
	if !sessions.loaded.IsRevoked || sessions.loaded.ControlProofHash != nil || sessions.controlRevokedSessionID == "" {
		t.Fatalf("controlled logout state = revoked %v proof %#v session %q", sessions.loaded.IsRevoked, sessions.loaded.ControlProofHash, sessions.controlRevokedSessionID)
	}
	if got, err := svc.ValidateSession(context.Background(), replacement); err == nil || got != nil {
		t.Fatalf("replacement after controlled logout = %#v, %v; want revoked", got, err)
	}
}

func TestValidateSessionWithControlRecoversConcurrentCASWinnerExpiry(t *testing.T) {
	svc, sessions, oldToken, proof := newControlledBrowserSessionFixture(t)
	winnerIssuedAt := time.Now().Add(-time.Minute)
	winnerToken, winnerClaims, err := pkgauth.GenerateTokenForSession(pkgauth.UserSession{
		ID: "user-1", SessionClass: OAuthSessionClassPlatformBrowser,
	}, sessions.loaded.ID, winnerIssuedAt, pkgauth.DefaultSessionLifetime)
	if err != nil {
		t.Fatalf("GenerateTokenForSession(winner): %v", err)
	}
	losesCAS := false
	sessions.controlRenewedResult = &losesCAS
	sessions.competingToken = winnerToken
	sessions.competingExpiresAt = winnerClaims.ExpiresAt.Time

	claims, replacement, proofExpiresAt, err := svc.ValidateSessionWithControl(context.Background(), oldToken, proof)
	if err != nil || claims == nil || replacement != winnerToken || !proofExpiresAt.Equal(winnerClaims.ExpiresAt.Time) {
		t.Fatalf("CAS loser recovery = %#v, %q, %v, %v; want winner %q at %v", claims, replacement, proofExpiresAt, err, winnerToken, winnerClaims.ExpiresAt.Time)
	}
	if sessions.loaded.Token != winnerToken || !sessions.loaded.ExpiresAt.Equal(winnerClaims.ExpiresAt.Time) {
		t.Fatalf("durable winner = %q at %v", sessions.loaded.Token, sessions.loaded.ExpiresAt)
	}
}

func TestBrowserLogoutWinsBeforeControlledRenewal(t *testing.T) {
	svc, sessions, oldToken, proof := newControlledBrowserSessionFixture(t)
	if err := svc.BrowserLogout(context.Background(), oldToken, proof); err != nil {
		t.Fatalf("BrowserLogout: %v", err)
	}
	if !sessions.loaded.IsRevoked || sessions.loaded.ControlProofHash != nil {
		t.Fatalf("browser logout state = revoked %v proof %#v", sessions.loaded.IsRevoked, sessions.loaded.ControlProofHash)
	}
	claims, replacement, proofExpiresAt, err := svc.ValidateSessionWithControl(context.Background(), oldToken, proof)
	if err == nil || claims != nil || replacement != "" || !proofExpiresAt.IsZero() {
		t.Fatalf("post-logout renewal = %#v, %q, %v, %v; want rejection", claims, replacement, proofExpiresAt, err)
	}
	if sessions.controlRenewedSessionID != "" {
		t.Fatalf("post-logout validation reached renewal write for %q", sessions.controlRenewedSessionID)
	}
}

func TestValidateSessionWithControlLeavesLegacyNullProofRowsStrictOnly(t *testing.T) {
	svc, sessions, oldToken, proof := newControlledBrowserSessionFixture(t)
	sessions.loaded.ControlProofHash = nil
	claims, replacement, proofExpiresAt, err := svc.ValidateSessionWithControl(context.Background(), oldToken, proof)
	if err != nil || claims == nil || replacement != "" || !proofExpiresAt.IsZero() {
		t.Fatalf("legacy current bearer = %#v, %q, %v, %v; want strict validation only", claims, replacement, proofExpiresAt, err)
	}
	winnerToken, winnerClaims, err := pkgauth.GenerateTokenForSession(pkgauth.UserSession{
		ID: "user-1", SessionClass: OAuthSessionClassPlatformBrowser,
	}, sessions.loaded.ID, time.Now(), pkgauth.DefaultSessionLifetime)
	if err != nil {
		t.Fatal(err)
	}
	sessions.loaded.Token = winnerToken
	sessions.loaded.ExpiresAt = winnerClaims.ExpiresAt.Time
	claims, replacement, proofExpiresAt, err = svc.ValidateSessionWithControl(context.Background(), oldToken, proof)
	if err == nil || claims != nil || replacement != "" || !proofExpiresAt.IsZero() {
		t.Fatalf("legacy superseded bearer = %#v, %q, %v, %v; want reauthentication", claims, replacement, proofExpiresAt, err)
	}
}

func TestValidateSessionWithControlFailsClosedWithoutProofWriteCapability(t *testing.T) {
	_, sessions, oldToken, proof := newControlledBrowserSessionFixture(t)
	legacyRepo := &sessionRepoWithoutControl{SessionRepository: sessions}
	svc := NewAuthService(&userRepoStub{user: &models.User{ID: "user-1", IsActive: true}}, legacyRepo, nil)

	claims, replacement, proofExpiresAt, err := svc.ValidateSessionWithControl(context.Background(), oldToken, proof)
	if err != nil || claims == nil || replacement != "" || !proofExpiresAt.IsZero() {
		t.Fatalf("current bearer without proof-write capability = %#v, %q, %v, %v; want strict validation", claims, replacement, proofExpiresAt, err)
	}
	if sessions.controlRenewedSessionID != "" {
		t.Fatalf("missing proof-write capability reached renewal for %q", sessions.controlRenewedSessionID)
	}

	winnerToken, winnerClaims, err := pkgauth.GenerateTokenForSession(pkgauth.UserSession{
		ID: "user-1", SessionClass: OAuthSessionClassPlatformBrowser,
	}, sessions.loaded.ID, time.Now(), pkgauth.DefaultSessionLifetime)
	if err != nil {
		t.Fatal(err)
	}
	sessions.loaded.Token = winnerToken
	sessions.loaded.ExpiresAt = winnerClaims.ExpiresAt.Time
	claims, replacement, proofExpiresAt, err = svc.ValidateSessionWithControl(context.Background(), oldToken, proof)
	if err == nil || claims != nil || replacement != "" || !proofExpiresAt.IsZero() {
		t.Fatalf("superseded bearer without proof-write capability = %#v, %q, %v, %v; want rejection", claims, replacement, proofExpiresAt, err)
	}
	if err := svc.BrowserLogout(context.Background(), oldToken, proof); err != nil {
		t.Fatalf("BrowserLogout without proof-write capability: %v", err)
	}
	if sessions.controlRevokedSessionID != "" || sessions.loaded.IsRevoked {
		t.Fatal("missing proof-write capability reached controlled revocation")
	}
}

func TestValidateSessionWithControlRejectsExpiredRevokedAndExternalRecovery(t *testing.T) {
	for _, state := range []string{"expired", "revoked"} {
		t.Run(state, func(t *testing.T) {
			svc, sessions, token, proof := newControlledBrowserSessionFixture(t)
			if state == "expired" {
				sessions.loaded.ExpiresAt = time.Now().Add(-time.Second)
			} else {
				sessions.loaded.IsRevoked = true
			}
			claims, replacement, proofExpiresAt, err := svc.ValidateSessionWithControl(context.Background(), token, proof)
			if err == nil || claims != nil || replacement != "" || !proofExpiresAt.IsZero() {
				t.Fatalf("%s controlled validation = %#v, %q, %v, %v; want rejection", state, claims, replacement, proofExpiresAt, err)
			}
		})
	}

	oldToken, oldClaims, err := pkgauth.GenerateToken(pkgauth.UserSession{
		ID: "user-1", ConnectedAppId: "app-1", Scope: "api", GrantType: "authorization_code",
	})
	if err != nil {
		t.Fatal(err)
	}
	proof, proofHash, err := newSessionControlProof()
	if err != nil {
		t.Fatal(err)
	}
	appID, scope, grant := "app-1", "api", "authorization_code"
	sessions := &sessionRepoStub{loaded: &models.SystemSession{
		ID: oldClaims.ID, UserID: "user-1", Token: oldToken, ExpiresAt: time.Now().Add(time.Hour),
		ConnectedAppID: &appID, Scope: &scope, GrantType: &grant, ControlProofHash: &proofHash,
	}}
	svc := NewAuthService(&userRepoStub{user: &models.User{ID: "user-1", IsActive: true}}, sessions, nil)
	claims, replacement, proofExpiresAt, err := svc.ValidateSessionWithControl(context.Background(), oldToken, proof)
	if err != nil || claims == nil || replacement != "" || !proofExpiresAt.IsZero() || sessions.controlRenewedSessionID != "" {
		t.Fatalf("current external session = %#v, %q, %v, %v; behavior changed", claims, replacement, proofExpiresAt, err)
	}
	if err := svc.BrowserLogout(context.Background(), oldToken, proof); err != nil {
		t.Fatalf("BrowserLogout(external): %v", err)
	}
	if sessions.loaded.IsRevoked || sessions.controlRevokedSessionID != "" {
		t.Fatal("browser-control logout changed external OAuth session behavior")
	}
	winnerToken, winnerClaims, err := pkgauth.GenerateTokenForSession(pkgauth.UserSession{
		ID: "user-1", ConnectedAppId: appID, Scope: scope, GrantType: grant,
	}, sessions.loaded.ID, time.Now().Add(-time.Minute), pkgauth.DefaultSessionLifetime)
	if err != nil {
		t.Fatal(err)
	}
	sessions.loaded.Token = winnerToken
	sessions.loaded.ExpiresAt = winnerClaims.ExpiresAt.Time
	claims, replacement, proofExpiresAt, err = svc.ValidateSessionWithControl(context.Background(), oldToken, proof)
	if err == nil || claims != nil || replacement != "" || !proofExpiresAt.IsZero() {
		t.Fatalf("external superseded recovery = %#v, %q, %v, %v; want rejection", claims, replacement, proofExpiresAt, err)
	}
}

func newControlledBrowserSessionFixture(t *testing.T) (*AuthService, *sessionRepoStub, string, string) {
	t.Helper()
	issuedAt := time.Now().Add(-23 * time.Hour)
	token, claims, err := pkgauth.GenerateTokenForSession(pkgauth.UserSession{
		ID: "user-1", SessionClass: OAuthSessionClassPlatformBrowser,
	}, "session-1", issuedAt, pkgauth.DefaultSessionLifetime)
	if err != nil {
		t.Fatalf("GenerateTokenForSession: %v", err)
	}
	proof, proofHash, err := newSessionControlProof()
	if err != nil {
		t.Fatalf("newSessionControlProof: %v", err)
	}
	sessions := &sessionRepoStub{loaded: &models.SystemSession{
		ID: claims.ID, UserID: "user-1", Token: token, ExpiresAt: time.Now().Add(10 * time.Minute),
		ControlProofHash: &proofHash,
	}}
	svc := NewAuthService(&userRepoStub{user: &models.User{ID: "user-1", IsActive: true}}, sessions, nil)
	return svc, sessions, token, proof
}

type sessionRepoWithoutControl struct {
	ports.SessionRepository
}

func TestValidateRuntimeSessionBuildsBearerFreeDurableProjection(t *testing.T) {
	scope := "records.read"
	appID := "app-1"
	grant := "authorization_code"
	sessions := &sessionRepoStub{loaded: &models.SystemSession{
		ID: "session-1", UserID: "user-1", AuthEpoch: 7, ExpiresAt: time.Now().Add(time.Hour),
		Scope: &scope, ConnectedAppID: &appID, GrantType: &grant,
	}}
	svc := NewAuthService(&userRepoStub{user: &models.User{
		ID: "user-1", Username: "user@example.test", Email: "user@example.test",
		FirstName: "Test", LastName: "User", ProfileID: "profile-1", IsActive: true,
	}}, sessions, nil)

	user, err := svc.ValidateRuntimeSession(context.Background(), "session-1", "user-1")
	if err != nil {
		t.Fatalf("ValidateRuntimeSession: %v", err)
	}
	if user.ID != "user-1" || user.ProfileID != "profile-1" || user.Scope != scope ||
		user.ConnectedAppId != appID || user.GrantType != grant {
		t.Fatalf("Runtime session projection = %#v", user)
	}
	if user.SessionClass != "" {
		t.Fatalf("Runtime projection copied JWT-only SessionClass: %q", user.SessionClass)
	}
	state, err := svc.ValidateRuntimeSessionState(context.Background(), "session-1", "user-1")
	if err != nil || state.AuthEpoch != 7 || state.User.ID != "user-1" {
		t.Fatalf("Runtime session state = %#v, %v; want durable AuthEpoch 7", state, err)
	}
}

func TestValidateRuntimeSessionFailsClosedWithoutBearerFallback(t *testing.T) {
	active := true
	inactive := false
	tests := []struct {
		name        string
		principalID string
		userActive  bool
		appActive   *bool
	}{
		{name: "principal mismatch", principalID: "other-user", userActive: true, appActive: &active},
		{name: "inactive principal bypasses warm cache", principalID: "user-1", userActive: false, appActive: &active},
		{name: "inactive connected app bypasses warm cache", principalID: "user-1", userActive: true, appActive: &inactive},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			appID := "app-1"
			sessions := &sessionRepoStub{loaded: &models.SystemSession{
				ID: "session-1", UserID: "user-1", AuthEpoch: 1, ExpiresAt: time.Now().Add(time.Hour), ConnectedAppID: &appID,
			}, connectedAppAlive: test.appActive}
			svc := NewAuthService(&userRepoStub{user: &models.User{
				ID: "user-1", ProfileID: "profile-1", IsActive: test.userActive,
			}}, sessions, nil)
			svc.activeCache.set("user-1", true)
			svc.connectedAppCache.set(appID, true)

			if user, err := svc.ValidateRuntimeSession(context.Background(), "session-1", test.principalID); err == nil || user != nil {
				t.Fatalf("ValidateRuntimeSession = %#v, %v; want fail closed", user, err)
			}
		})
	}
}

func TestAuthServiceOnConnectedAppChangedInvalidatesConnectedAppCache(t *testing.T) {
	svc := NewAuthService(nil, nil, nil)
	svc.connectedAppCache.set("app-001", true)

	svc.OnConnectedAppChanged("app-001")

	if _, ok := svc.connectedAppCache.get("app-001"); ok {
		t.Fatal("connected app active-state cache still contains invalidated app")
	}
}

func TestActiveUserCachePrunesExpiredEntryOnRead(t *testing.T) {
	cache := newActiveUserCache()
	cache.set("user-001", true)

	cache.mu.Lock()
	entry := cache.entries["user-001"]
	entry.cachedAt = time.Now().Add(-2 * activeUserCacheTTL)
	cache.entries["user-001"] = entry
	cache.mu.Unlock()

	if _, ok := cache.get("user-001"); ok {
		t.Fatal("expired active-user cache entry returned as a hit")
	}
	cache.mu.RLock()
	_, exists := cache.entries["user-001"]
	cache.mu.RUnlock()
	if exists {
		t.Fatal("expired active-user cache entry was not pruned")
	}
}

func TestAuthAsyncWriteContextCopiesOnlyObservability(t *testing.T) {
	type unsafeKey struct{}

	sourceCtx := context.WithValue(context.Background(), unsafeKey{}, "request-scoped")
	sourceCtx = context.WithValue(sourceCtx, constants.ContextKeyTraceparent, "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
	sourceCtx = context.WithValue(sourceCtx, constants.ContextKeyRequestID, "req-123")
	sourceCtx, cancelSource := context.WithCancel(sourceCtx)
	cancelSource()

	traceparent, requestID := authAsyncWriteObservability(sourceCtx)
	asyncCtx, cancel := authAsyncWriteContext(traceparent, requestID)
	defer cancel()

	if err := asyncCtx.Err(); err != nil {
		t.Fatalf("async context inherited source cancellation: %v", err)
	}
	if _, ok := asyncCtx.Deadline(); !ok {
		t.Fatal("async context should be bounded by a deadline")
	}
	if got, _ := asyncCtx.Value(constants.ContextKeyTraceparent).(string); got != "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" {
		t.Fatalf("traceparent = %q", got)
	}
	if got, _ := asyncCtx.Value(constants.ContextKeyRequestID).(string); got != "req-123" {
		t.Fatalf("request ID = %q", got)
	}
	if got := asyncCtx.Value(unsafeKey{}); got != nil {
		t.Fatalf("unsafe request-scoped value leaked into async context: %#v", got)
	}
}

func TestTouchSessionCoalescesConcurrentSameSessionUpdates(t *testing.T) {
	repo := &touchSessionRepo{updates: make(chan string, 10)}
	svc := NewAuthService(nil, repo, nil)
	svc.sessionTouch = newSessionTouchCoalescer(time.Minute)
	svc.sessionTouchSlots = make(chan struct{}, defaultSessionTouchConcurrency)

	for i := 0; i < 200; i++ {
		svc.TouchSession(context.Background(), "sess-1")
	}

	select {
	case got := <-repo.updates:
		if got != "sess-1" {
			t.Fatalf("updated session = %q, want sess-1", got)
		}
	case <-time.After(time.Second):
		t.Fatal("timed out waiting for first touch update")
	}

	time.Sleep(50 * time.Millisecond)
	if got := atomic.LoadInt32(&repo.count); got != 1 {
		t.Fatalf("UpdateLastActivity calls = %d, want 1", got)
	}
}

func TestTouchSessionAllowsNextUpdateAfterCoalesceWindow(t *testing.T) {
	repo := &touchSessionRepo{updates: make(chan string, 10)}
	svc := NewAuthService(nil, repo, nil)
	svc.sessionTouch = newSessionTouchCoalescer(5 * time.Millisecond)
	svc.sessionTouchSlots = make(chan struct{}, defaultSessionTouchConcurrency)

	svc.TouchSession(context.Background(), "sess-1")
	select {
	case <-repo.updates:
	case <-time.After(time.Second):
		t.Fatal("timed out waiting for first touch update")
	}

	time.Sleep(20 * time.Millisecond)
	svc.TouchSession(context.Background(), "sess-1")
	select {
	case <-repo.updates:
	case <-time.After(time.Second):
		t.Fatal("timed out waiting for second touch update")
	}

	if got := atomic.LoadInt32(&repo.count); got != 2 {
		t.Fatalf("UpdateLastActivity calls = %d, want 2", got)
	}
}

func TestSessionTouchCoalescerPrunesExpiredEntries(t *testing.T) {
	coalescer := newSessionTouchCoalescer(time.Minute)
	coalescer.maxTracked = 2

	old := time.Now().Add(-2 * time.Minute)
	coalescer.entries["old-1"] = sessionTouchState{lastQueued: old}
	coalescer.entries["old-2"] = sessionTouchState{lastQueued: old}
	coalescer.entries["busy"] = sessionTouchState{lastQueued: old, inFlight: true}

	if !coalescer.shouldQueue("new", time.Now()) {
		t.Fatal("new session touch was not queued")
	}

	if _, ok := coalescer.entries["old-1"]; ok {
		t.Fatal("expired idle session touch entry was not pruned")
	}
	if _, ok := coalescer.entries["busy"]; !ok {
		t.Fatal("in-flight session touch entry was pruned")
	}
	if _, ok := coalescer.entries["new"]; !ok {
		t.Fatal("new session touch entry was not tracked")
	}
}

type touchSessionRepo struct {
	count   int32
	updates chan string
}

func (r *touchSessionRepo) InsertSession(context.Context, *models.SystemSession) error { return nil }
func (r *touchSessionRepo) GetSession(context.Context, string) (*models.SystemSession, error) {
	return nil, nil
}
func (r *touchSessionRepo) AdvanceSessionAuthEpoch(context.Context, string, uint64) (uint64, error) {
	return 0, nil
}
func (r *touchSessionRepo) RevokeSession(context.Context, string, string) (bool, error) {
	return true, nil
}
func (r *touchSessionRepo) RevokeAllSessionsForUser(context.Context, string) error { return nil }
func (r *touchSessionRepo) RevokeAllSessionsByProfileID(context.Context, string) (int64, error) {
	return 0, nil
}
func (r *touchSessionRepo) RevokeAllSessionsByUserRoleID(context.Context, string) (int64, error) {
	return 0, nil
}
func (r *touchSessionRepo) RevokeAllSessionsForConnectedApp(context.Context, string) (int64, error) {
	return 0, nil
}
func (r *touchSessionRepo) RevokeSessionsForUserAndConnectedApp(context.Context, string, string) (int64, error) {
	return 0, nil
}
func (r *touchSessionRepo) RenewSession(context.Context, string, string, string, time.Time, time.Time) (bool, error) {
	return true, nil
}
func (r *touchSessionRepo) UpdateLastActivity(_ context.Context, sessionID string) error {
	atomic.AddInt32(&r.count, 1)
	r.updates <- sessionID
	return nil
}
func (r *touchSessionRepo) CleanupExpiredSessions(context.Context, time.Duration) (int64, error) {
	return 0, nil
}
func (r *touchSessionRepo) IsConnectedAppActive(context.Context, string) (bool, error) {
	return true, nil
}
