package auth

import (
	"context"
	stderrors "errors"
	"net/http"
	"strings"
	"testing"
	"time"

	kernelauth "github.com/nexuscrm/kernel/pkg/auth"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
)

type authorizingSessionRepoStub struct {
	*sessionRepoStub
	state     *ports.AuthorizingSessionState
	loadState func() *ports.AuthorizingSessionState
	err       error
	calls     int
}

func runtimeAuthorizingToken(
	t *testing.T,
	sessionID, userID, profileID string,
	roleID *string,
) string {
	t.Helper()
	roleName := "Current role"
	if roleID == nil {
		roleName = ""
	}
	token, _, err := kernelauth.GenerateTokenForSession(
		kernelauth.UserSession{
			ID: userID, Name: "Current user", Email: "current@example.com",
			ProfileId: profileID, ProfileName: "Current profile",
			RoleId: roleID, RoleName: authorizingOptionalString(roleName),
			SessionClass: "interactive_browser",
		},
		sessionID,
		time.Now(),
		time.Hour,
	)
	if err != nil {
		t.Fatalf("GenerateTokenForSession: %v", err)
	}
	return token
}

func (repository *authorizingSessionRepoStub) LoadAuthorizingSessionState(
	context.Context,
	string,
) (*ports.AuthorizingSessionState, error) {
	repository.calls++
	if repository.loadState != nil {
		return repository.loadState(), repository.err
	}
	return repository.state, repository.err
}

func TestAdmitAuthorizingSessionPreservesSessionIdentityAndStrongFloor(t *testing.T) {
	roleID := "role-current"
	roleName := "Current role"
	token, tokenClaims, err := kernelauth.GenerateToken(kernelauth.UserSession{
		ID:          "user-current",
		ProfileId:   "profile-current",
		ProfileName: "Current profile",
		RoleId:      &roleID,
		RoleName:    &roleName,
	})
	if err != nil {
		t.Fatalf("GenerateToken: %v", err)
	}
	subject := authorizingSubject(t, "user-current", "profile-current")
	floor := authorizingVersion(t, 23)
	repository := &authorizingSessionRepoStub{
		sessionRepoStub: &sessionRepoStub{},
		state: &ports.AuthorizingSessionState{
			Session: models.SystemSession{
				ID:        tokenClaims.ID,
				UserID:    subject.UserID(),
				Token:     token,
				ExpiresAt: time.Now().Add(time.Hour), AuthEpoch: 7,
			},
			Subject:                subject,
			UserRoleID:             "role-current",
			UserAuthorized:         true,
			ConnectedAppAuthorized: true,
			PermissionFloor:        floor,
		},
	}
	service := NewAuthService(nil, repository, nil)

	validation, err := service.AdmitAuthorizingSession(
		context.Background(),
		token,
	)
	if err != nil {
		t.Fatalf("AdmitAuthorizingSession: %v", err)
	}
	if validation == nil || validation.Claims == nil {
		t.Fatal("AdmitAuthorizingSession returned empty validation")
	}
	if repository.calls != 1 {
		t.Fatalf("strong repository reads = %d, want 1", repository.calls)
	}
	if validation.Subject.UserID() != "user-current" ||
		validation.Subject.ProfileID() != "profile-current" ||
		validation.PermissionFloor.Revision() != 23 {
		t.Fatalf("validation = %#v", validation)
	}
	user := validation.Claims.User
	if user.ID != "user-current" ||
		validation.Claims.Subject != "user-current" ||
		user.ProfileId != "profile-current" ||
		user.RoleId == nil ||
		*user.RoleId != "role-current" {
		t.Fatalf("current claims projection = %#v", user)
	}
	if user.ProfileName != "Current profile" ||
		user.RoleName == nil ||
		*user.RoleName != "Current role" {
		t.Fatalf("session display identities = %#v", user)
	}
}

func TestAdmitRuntimeIdentitySessionReturnsCompleteAuthorityFromOneStrongRead(t *testing.T) {
	roleID := "role-current"
	connectedAppID := "connected-app-1"
	scope := "api runtime"
	grantType := "authorization_code"
	token := runtimeAuthorizingToken(t, "session-1", "user-1", "profile-1", &roleID)
	subject := authorizingSubject(t, "user-1", "profile-1")
	repository := &authorizingSessionRepoStub{
		sessionRepoStub: &sessionRepoStub{},
		state: &ports.AuthorizingSessionState{
			Session: models.SystemSession{
				ID: "session-1", UserID: "user-1", Token: token,
				ExpiresAt: time.Now().Add(time.Hour), AuthEpoch: 7,
				ConnectedAppID: &connectedAppID, Scope: &scope, GrantType: &grantType,
			},
			Subject: subject, UserRoleID: roleID,
			UserAuthorized: true, ConnectedAppAuthorized: true,
			PermissionFloor: authorizingVersion(t, 43),
		},
	}
	validation, err := NewAuthService(nil, repository, nil).
		AdmitRuntimeIdentitySession(context.Background(), token)
	if err != nil {
		t.Fatal(err)
	}
	if repository.calls != 1 {
		t.Fatalf("strong repository reads = %d, want 1", repository.calls)
	}
	if validation == nil || validation.SessionID != "session-1" || validation.AuthEpoch != 7 ||
		validation.User.ID != "user-1" || validation.User.ProfileID != "profile-1" ||
		validation.User.ConnectedAppId != connectedAppID || validation.User.Scope != scope ||
		validation.User.GrantType != grantType || validation.User.SessionClass != "" ||
		validation.Subject.UserID() != "user-1" || validation.PermissionFloor.Revision() != 43 {
		t.Fatalf("Runtime Identity validation = %#v", validation)
	}
}

func TestMCPAudienceAdmissionDoesNotRelaxOrdinaryHostAdmission(t *testing.T) {
	t.Setenv("API_BASE_URL", "https://nexus.example")
	const mcpResource = "https://nexus.example/mcp"
	token, tokenClaims, err := kernelauth.GenerateTokenForSessionWithAudience(
		kernelauth.UserSession{ID: "user-current", ProfileId: "profile-current"},
		"mcp-session",
		time.Now(),
		kernelauth.DefaultSessionLifetime,
		mcpResource,
	)
	if err != nil {
		t.Fatalf("GenerateTokenForSessionWithAudience: %v", err)
	}
	subject := authorizingSubject(t, "user-current", "profile-current")
	repository := &authorizingSessionRepoStub{
		sessionRepoStub: &sessionRepoStub{},
		state: &ports.AuthorizingSessionState{
			Session: models.SystemSession{ID: tokenClaims.ID, UserID: subject.UserID(), Token: token, ExpiresAt: time.Now().Add(time.Hour)},
			Subject: subject, UserAuthorized: true, ConnectedAppAuthorized: true, PermissionFloor: authorizingVersion(t, 24),
		},
	}
	service := NewAuthService(nil, repository, nil)
	if validation, err := service.AdmitAuthorizingSession(context.Background(), token); validation != nil || httpStatus(err) != http.StatusUnauthorized {
		t.Fatalf("ordinary admission = %#v, %T(%v); want audience rejection", validation, err, err)
	}
	validation, err := service.AdmitAuthorizingSessionForAudience(context.Background(), token, mcpResource)
	if err != nil || validation == nil || validation.Claims == nil {
		t.Fatalf("MCP audience admission = %#v, %v", validation, err)
	}
}

func TestAdmitAuthorizingSessionRejectsSessionIdentityDrift(t *testing.T) {
	for _, test := range []struct {
		name       string
		userID     string
		profileID  string
		userRoleID string
	}{
		{name: "user", userID: "user-current", profileID: "profile-token", userRoleID: "role-token"},
		{name: "profile", userID: "user-token", profileID: "profile-current", userRoleID: "role-token"},
		{name: "role", userID: "user-token", profileID: "profile-token", userRoleID: "role-current"},
		{name: "removed role", userID: "user-token", profileID: "profile-token"},
	} {
		t.Run(test.name, func(t *testing.T) {
			roleID := "role-token"
			roleName := "Token role"
			token, tokenClaims, err := kernelauth.GenerateToken(kernelauth.UserSession{
				ID:          "user-token",
				ProfileId:   "profile-token",
				ProfileName: "Token profile",
				RoleId:      &roleID,
				RoleName:    &roleName,
			})
			if err != nil {
				t.Fatalf("GenerateToken: %v", err)
			}
			subject := authorizingSubject(t, test.userID, test.profileID)
			repository := &authorizingSessionRepoStub{
				sessionRepoStub: &sessionRepoStub{},
				state: &ports.AuthorizingSessionState{
					Session: models.SystemSession{
						ID:        tokenClaims.ID,
						UserID:    subject.UserID(),
						Token:     token,
						ExpiresAt: time.Now().Add(time.Hour),
					},
					Subject:                subject,
					UserRoleID:             test.userRoleID,
					UserAuthorized:         true,
					ConnectedAppAuthorized: true,
					PermissionFloor:        authorizingVersion(t, 23),
				},
			}

			validation, err := NewAuthService(nil, repository, nil).
				AdmitAuthorizingSession(context.Background(), token)
			if validation != nil || httpStatus(err) != http.StatusUnauthorized {
				t.Fatalf(
					"identity drift admission = %#v, %T(%v); want unauthorized",
					validation,
					err,
					err,
				)
			}
		})
	}
}

func TestAdmitRuntimeAuthorizingSessionUsesOneBearerFreeStrongRead(t *testing.T) {
	scope := "api runtime"
	connectedAppID := "connected-app-1"
	grantType := "authorization_code"
	roleID := "role-current"
	subject := authorizingSubject(t, "user-1", "profile-current")
	repository := &authorizingSessionRepoStub{
		sessionRepoStub: &sessionRepoStub{},
		state: &ports.AuthorizingSessionState{
			Session: models.SystemSession{
				ID: "session-1", UserID: "user-1",
				Token:     runtimeAuthorizingToken(t, "session-1", "user-1", "profile-current", &roleID),
				ExpiresAt: time.Now().Add(time.Hour), AuthEpoch: 7,
				Scope: &scope, ConnectedAppID: &connectedAppID, GrantType: &grantType,
			},
			Subject: subject, UserRoleID: "role-current",
			UserAuthorized: true, ConnectedAppAuthorized: true,
			PermissionFloor: authorizingVersion(t, 43),
		},
	}
	service := NewAuthService(nil, repository, nil)
	if !service.AuthorizingSessionAdmissionReady() {
		t.Fatal("strong authorizing-session capability was not detected")
	}

	validation, err := service.AdmitRuntimeAuthorizingSession(
		context.Background(),
		"session-1",
		"user-1",
	)
	if err != nil {
		t.Fatalf("AdmitRuntimeAuthorizingSession: %v", err)
	}
	if repository.calls != 1 {
		t.Fatalf("strong repository reads = %d, want 1", repository.calls)
	}
	if validation == nil ||
		validation.AuthEpoch != 7 ||
		validation.Subject.ProfileID() != "profile-current" ||
		validation.PermissionFloor.Revision() != 43 ||
		validation.User.ID != "user-1" ||
		validation.User.ProfileID != "profile-current" ||
		validation.User.RoleID == nil ||
		*validation.User.RoleID != "role-current" ||
		validation.User.Name != "Current user" ||
		validation.User.Email == nil ||
		*validation.User.Email != "current@example.com" ||
		validation.User.ProfileName != "Current profile" ||
		validation.User.RoleName == nil ||
		*validation.User.RoleName != "Current role" ||
		validation.User.SessionClass != "" ||
		validation.User.Scope != scope ||
		validation.User.ConnectedAppId != connectedAppID ||
		validation.User.GrantType != grantType {
		t.Fatalf("Runtime authorizing validation = %#v", validation)
	}
}

func TestAdmitRuntimeAuthorizingSessionSeparatesCredentialAndAuthorityFailures(t *testing.T) {
	service := NewAuthService(nil, &sessionRepoStub{}, nil)
	validation, err := service.AdmitRuntimeAuthorizingSession(
		context.Background(),
		"session-1",
		"user-1",
	)
	if validation != nil {
		t.Fatalf("missing authority validation = %#v", validation)
	}
	var failure *permissionstatev1.Failure
	if !stderrors.As(err, &failure) ||
		failure.Category != permissionstatev1.FailureAuthorityUnavailable {
		t.Fatalf("missing authority error = %T(%v)", err, err)
	}

	repository := &authorizingSessionRepoStub{
		sessionRepoStub: &sessionRepoStub{},
		state: &ports.AuthorizingSessionState{
			Session: models.SystemSession{
				ID: "session-1", UserID: "user-1", Token: "current-bearer",
				ExpiresAt: time.Now().Add(time.Hour), AuthEpoch: 1,
			},
			Subject:        authorizingSubject(t, "user-1", "profile-1"),
			UserAuthorized: false, ConnectedAppAuthorized: true,
			PermissionFloor: authorizingVersion(t, 1),
		},
	}
	validation, err = NewAuthService(nil, repository, nil).
		AdmitRuntimeAuthorizingSession(context.Background(), "session-1", "user-1")
	if validation != nil || httpStatus(err) != http.StatusUnauthorized {
		t.Fatalf("revoked Runtime credential = %#v, %T(%v)", validation, err, err)
	}
}

func TestEveryJWTAuthorizingAdmissionModeUsesStrongRepository(t *testing.T) {
	for _, test := range []struct {
		name string
		call func(
			*AuthService,
			context.Context,
			string,
		) (*AuthorizingSessionValidation, error)
	}{
		{
			name: "strict",
			call: func(
				service *AuthService,
				ctx context.Context,
				token string,
			) (*AuthorizingSessionValidation, error) {
				return service.AdmitAuthorizingSession(ctx, token)
			},
		},
		{
			name: "renewal",
			call: func(
				service *AuthService,
				ctx context.Context,
				token string,
			) (*AuthorizingSessionValidation, error) {
				validation, _, err := service.
					AdmitAuthorizingSessionWithRenewal(ctx, token)
				return validation, err
			},
		},
		{
			name: "browser control",
			call: func(
				service *AuthService,
				ctx context.Context,
				token string,
			) (*AuthorizingSessionValidation, error) {
				validation, _, _, err := service.
					AdmitAuthorizingSessionWithControl(ctx, token, "")
				return validation, err
			},
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			token, tokenClaims, err := kernelauth.GenerateToken(
				kernelauth.UserSession{
					ID:          "user-1",
					ProfileId:   "profile-1",
					ProfileName: "Profile 1",
				},
			)
			if err != nil {
				t.Fatalf("GenerateToken: %v", err)
			}
			subject := authorizingSubject(t, "user-1", "profile-1")
			repository := &authorizingSessionRepoStub{
				sessionRepoStub: &sessionRepoStub{},
				state: &ports.AuthorizingSessionState{
					Session: models.SystemSession{
						ID:        tokenClaims.ID,
						UserID:    "user-1",
						Token:     token,
						ExpiresAt: time.Now().Add(kernelauth.DefaultSessionLifetime),
					},
					Subject:                subject,
					UserAuthorized:         true,
					ConnectedAppAuthorized: true,
					PermissionFloor:        authorizingVersion(t, 31),
				},
			}
			validation, err := test.call(
				NewAuthService(nil, repository, nil),
				context.Background(),
				token,
			)
			if err != nil {
				t.Fatalf("admission: %v", err)
			}
			if repository.calls != 1 {
				t.Fatalf("strong repository reads = %d, want 1", repository.calls)
			}
			if validation == nil ||
				validation.PermissionFloor.Revision() != 31 ||
				validation.Claims.User.ProfileName != "Profile 1" {
				t.Fatalf("validation = %#v", validation)
			}
		})
	}
}

func TestAuthorizingRenewalCASRecoveryUsesStrongRepository(t *testing.T) {
	issuedAt := time.Now().Add(-23 * time.Hour)
	oldRoleID := "role-1"
	oldRoleName := "Role 1"
	oldToken, oldClaims, err := kernelauth.GenerateTokenForSession(
		kernelauth.UserSession{
			ID:           "user-1",
			ProfileId:    "profile-1",
			ProfileName:  "Profile 1",
			RoleId:       &oldRoleID,
			RoleName:     &oldRoleName,
			SessionClass: OAuthSessionClassPlatformBrowser,
		},
		"session-1",
		issuedAt,
		kernelauth.DefaultSessionLifetime,
	)
	if err != nil {
		t.Fatal(err)
	}
	winnerToken, winnerClaims, err := kernelauth.GenerateTokenForSession(
		kernelauth.UserSession{
			ID:           "user-1",
			ProfileId:    "profile-1",
			ProfileName:  "Profile 1",
			RoleId:       &oldRoleID,
			RoleName:     &oldRoleName,
			SessionClass: OAuthSessionClassPlatformBrowser,
		},
		oldClaims.ID,
		time.Now().Add(-renewedBearerOverlapWindow/2),
		kernelauth.DefaultSessionLifetime,
	)
	if err != nil {
		t.Fatal(err)
	}
	losesCAS := false
	sessions := &sessionRepoStub{
		loaded: &models.SystemSession{
			ID:        oldClaims.ID,
			UserID:    "user-1",
			Token:     oldToken,
			ExpiresAt: time.Now().Add(10 * time.Minute),
		},
		renewedResult:      &losesCAS,
		competingToken:     winnerToken,
		competingExpiresAt: winnerClaims.ExpiresAt.Time,
	}
	repository := &authorizingSessionRepoStub{sessionRepoStub: sessions}
	repository.loadState = func() *ports.AuthorizingSessionState {
		revision := uint64(37)
		if repository.calls == 2 {
			revision = 38
		}
		return &ports.AuthorizingSessionState{
			Session:                *sessions.loaded,
			Subject:                authorizingSubject(t, "user-1", "profile-1"),
			UserRoleID:             "role-1",
			UserAuthorized:         true,
			ConnectedAppAuthorized: true,
			PermissionFloor:        authorizingVersion(t, revision),
		}
	}

	validation, replacement, err := NewAuthService(nil, repository, nil).
		AdmitAuthorizingSessionWithRenewal(context.Background(), oldToken)
	if err != nil {
		t.Fatalf(
			"AdmitAuthorizingSessionWithRenewal(): %v after %d strong reads; durable token current=%t expires=%v old claim=%v",
			err,
			repository.calls,
			sessions.loaded.Token == winnerToken,
			sessions.loaded.ExpiresAt,
			oldClaims.ExpiresAt.Time,
		)
	}
	if validation == nil || replacement != winnerToken {
		t.Fatalf("CAS recovery = %#v, %q; want winner %q", validation, replacement, winnerToken)
	}
	if validation.Subject.ProfileID() != "profile-1" ||
		validation.PermissionFloor.Revision() != 38 ||
		validation.Claims.User.ProfileName != "Profile 1" ||
		validation.Claims.User.RoleId == nil ||
		*validation.Claims.User.RoleId != "role-1" ||
		validation.Claims.User.RoleName == nil ||
		*validation.Claims.User.RoleName != "Role 1" {
		t.Fatalf("CAS recovery = %#v", validation)
	}
	if repository.calls != 2 {
		t.Fatalf("strong repository reads = %d, want initial admission and CAS recovery", repository.calls)
	}
}

func TestAuthorizingControlledRenewalCASRecoveryUsesStrongRepository(t *testing.T) {
	issuedAt := time.Now().Add(-23 * time.Hour)
	oldRoleID := "role-1"
	oldRoleName := "Role 1"
	oldToken, oldClaims, err := kernelauth.GenerateTokenForSession(
		kernelauth.UserSession{
			ID:           "user-1",
			ProfileId:    "profile-1",
			ProfileName:  "Profile 1",
			RoleId:       &oldRoleID,
			RoleName:     &oldRoleName,
			SessionClass: OAuthSessionClassPlatformBrowser,
		},
		"session-1",
		issuedAt,
		kernelauth.DefaultSessionLifetime,
	)
	if err != nil {
		t.Fatal(err)
	}
	proof, proofHash, err := newSessionControlProof()
	if err != nil {
		t.Fatal(err)
	}
	winnerToken, winnerClaims, err := kernelauth.GenerateTokenForSession(
		kernelauth.UserSession{
			ID:           "user-1",
			ProfileId:    "profile-1",
			ProfileName:  "Profile 1",
			RoleId:       &oldRoleID,
			RoleName:     &oldRoleName,
			SessionClass: OAuthSessionClassPlatformBrowser,
		},
		oldClaims.ID,
		time.Now().Add(-renewedBearerOverlapWindow/2),
		kernelauth.DefaultSessionLifetime,
	)
	if err != nil {
		t.Fatal(err)
	}
	losesCAS := false
	sessions := &sessionRepoStub{
		loaded: &models.SystemSession{
			ID:               oldClaims.ID,
			UserID:           "user-1",
			Token:            oldToken,
			ExpiresAt:        time.Now().Add(10 * time.Minute),
			ControlProofHash: &proofHash,
		},
		controlRenewedResult: &losesCAS,
		competingToken:       winnerToken,
		competingExpiresAt:   winnerClaims.ExpiresAt.Time,
	}
	repository := &authorizingSessionRepoStub{sessionRepoStub: sessions}
	repository.loadState = func() *ports.AuthorizingSessionState {
		revision := uint64(41)
		if repository.calls == 2 {
			revision = 42
		}
		return &ports.AuthorizingSessionState{
			Session:                *sessions.loaded,
			Subject:                authorizingSubject(t, "user-1", "profile-1"),
			UserRoleID:             "role-1",
			UserAuthorized:         true,
			ConnectedAppAuthorized: true,
			PermissionFloor:        authorizingVersion(t, revision),
		}
	}

	validation, replacement, expiresAt, err := NewAuthService(nil, repository, nil).
		AdmitAuthorizingSessionWithControl(context.Background(), oldToken, proof)
	if err != nil {
		t.Fatalf("AdmitAuthorizingSessionWithControl(): %v", err)
	}
	if validation == nil ||
		replacement != winnerToken ||
		!expiresAt.Equal(winnerClaims.ExpiresAt.Time) {
		t.Fatalf(
			"controlled CAS recovery = %#v, %q, %v; want winner %q at %v",
			validation,
			replacement,
			expiresAt,
			winnerToken,
			winnerClaims.ExpiresAt.Time,
		)
	}
	if repository.calls != 2 {
		t.Fatalf("strong repository reads = %d, want initial admission and CAS recovery", repository.calls)
	}
	if validation.Subject.ProfileID() != "profile-1" ||
		validation.PermissionFloor.Revision() != 42 ||
		validation.Claims.User.ProfileName != "Profile 1" ||
		validation.Claims.User.RoleId == nil ||
		*validation.Claims.User.RoleId != "role-1" ||
		validation.Claims.User.RoleName == nil ||
		*validation.Claims.User.RoleName != "Role 1" {
		t.Fatalf("controlled CAS recovery = %#v", validation)
	}
}

func TestAuthorizingAdmissionSeparatesCredentialAndAuthorityFailures(t *testing.T) {
	service := NewAuthService(nil, &sessionRepoStub{}, nil)
	if validation, err := service.AdmitAuthorizingSession(
		context.Background(),
		"not-a-token",
	); validation != nil || httpStatus(err) != http.StatusUnauthorized {
		t.Fatalf(
			"malformed credential = %#v, %T(%v); want unauthorized",
			validation,
			err,
			err,
		)
	}

	token, _, err := kernelauth.GenerateToken(
		kernelauth.UserSession{ID: "user-1"},
	)
	if err != nil {
		t.Fatalf("GenerateToken: %v", err)
	}
	validation, err := service.AdmitAuthorizingSession(
		context.Background(),
		token,
	)
	if validation != nil {
		t.Fatalf("missing authority validation = %#v", validation)
	}
	var failure *permissionstatev1.Failure
	if !stderrors.As(err, &failure) ||
		failure.Category != permissionstatev1.FailureAuthorityUnavailable {
		t.Fatalf("missing authority error = %T(%v)", err, err)
	}
}

func TestAuthorizingAdmissionDoesNotLeakRepositoryFailure(t *testing.T) {
	token, _, err := kernelauth.GenerateToken(
		kernelauth.UserSession{ID: "user-1"},
	)
	if err != nil {
		t.Fatalf("GenerateToken: %v", err)
	}
	internal := stderrors.New("dial tcp secret-db.internal:4000")
	repository := &authorizingSessionRepoStub{
		sessionRepoStub: &sessionRepoStub{},
		err:             internal,
	}
	validation, err := NewAuthService(nil, repository, nil).
		AdmitAuthorizingSession(context.Background(), token)
	if validation != nil || err == nil {
		t.Fatalf("admission = %#v, %v; want authority failure", validation, err)
	}
	if strings.Contains(err.Error(), "secret-db.internal") {
		t.Fatalf("authority failure leaked repository detail: %v", err)
	}
	caused, ok := err.(interface{ Cause() error })
	if !ok || !stderrors.Is(caused.Cause(), internal) {
		t.Fatalf("internal cause = %T %#v, want original error", err, caused)
	}
}

func authorizingSubject(
	t *testing.T,
	userID string,
	profileID string,
) permissionstatev1.Subject {
	t.Helper()
	subject, err := permissionstatev1.NewSubject(userID, profileID)
	if err != nil {
		t.Fatalf("NewSubject: %v", err)
	}
	return subject
}

func authorizingVersion(
	t *testing.T,
	revision uint64,
) permissionstatev1.Version {
	t.Helper()
	lineage, err := permissionstatev1.LineageFromBytes(
		[]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
	)
	if err != nil {
		t.Fatalf("LineageFromBytes: %v", err)
	}
	version, err := permissionstatev1.NewVersion(lineage, revision)
	if err != nil {
		t.Fatalf("NewVersion: %v", err)
	}
	return version
}

func httpStatus(err error) int {
	if status, ok := err.(interface{ HTTPStatus() int }); ok {
		return status.HTTPStatus()
	}
	return 0
}

func TestAuthorizingPreparationFailureLeavesRenewalRetryable(t *testing.T) {
	for _, controlled := range []bool{false, true} {
		t.Run(map[bool]string{false: "bearer", true: "controlled"}[controlled], func(t *testing.T) {
			oldToken, claims, err := kernelauth.GenerateTokenForSession(kernelauth.UserSession{
				ID: "user-1", ProfileId: "profile-1", ProfileName: "Profile 1", SessionClass: OAuthSessionClassPlatformBrowser,
			}, "session-1", time.Now().Add(-23*time.Hour), kernelauth.DefaultSessionLifetime)
			if err != nil {
				t.Fatal(err)
			}
			proof, proofHash, err := newSessionControlProof()
			if err != nil {
				t.Fatal(err)
			}
			sessions := &sessionRepoStub{loaded: &models.SystemSession{ID: claims.ID, UserID: "user-1", Token: oldToken, ExpiresAt: time.Now().Add(10 * time.Minute)}}
			if controlled {
				sessions.loaded.ControlProofHash = &proofHash
			}
			repository := &authorizingSessionRepoStub{sessionRepoStub: sessions}
			repository.loadState = func() *ports.AuthorizingSessionState {
				return &ports.AuthorizingSessionState{Session: *sessions.loaded, Subject: authorizingSubject(t, "user-1", "profile-1"), UserAuthorized: true, ConnectedAppAuthorized: true, PermissionFloor: authorizingVersion(t, 1)}
			}
			service := NewAuthService(nil, repository, nil)
			read := func(token, presentedProof string, prepare func(*AuthorizingSessionValidation) error) (string, error) {
				if controlled {
					_, replacement, _, err := service.AdmitAuthorizingSessionWithControlPreparation(context.Background(), token, presentedProof, prepare)
					return replacement, err
				}
				_, replacement, err := service.AdmitAuthorizingSessionWithRenewalPreparation(context.Background(), token, prepare)
				return replacement, err
			}
			failure := stderrors.New("dependent read unavailable")
			calls := 0
			prepare := func(admitted *AuthorizingSessionValidation) error {
				calls++
				if admitted == nil || admitted.Claims.User.ID != "user-1" {
					t.Fatal("preparation lacks admitted identity")
				}
				return failure
			}
			if _, err := read("invalid-bearer", proof, prepare); err == nil || calls != 0 {
				t.Fatal("preparation ran without bearer admission")
			}
			if controlled {
				// A valid current bearer with an invalid proof retains the existing
				// read-only admission behavior; it must never renew the pair.
				if replacement, err := read(oldToken, "wrong-proof", func(*AuthorizingSessionValidation) error { return nil }); err != nil || replacement != "" || sessions.controlRenewedToken != "" {
					t.Fatalf("invalid proof changed read-only admission: replacement=%t err=%v", replacement != "", err)
				}
			}
			replacement, err := read(oldToken, proof, prepare)
			if !stderrors.Is(err, failure) || replacement != "" || calls != 1 {
				t.Fatalf("failure result: replacement=%t calls=%d err=%v", replacement != "", calls, err)
			}
			if sessions.loaded.Token != oldToken || sessions.renewedToken != "" || sessions.controlRenewedToken != "" {
				t.Fatal("failed preparation rotated the session")
			}
			replacement, err = read(oldToken, proof, func(*AuthorizingSessionValidation) error { return nil })
			if err != nil || replacement == "" || replacement == oldToken {
				t.Fatalf("original credentials cannot retry renewal: replacement=%t err=%v", replacement != "", err)
			}
		})
	}
}

func TestRuntimeResourceProjectionKeepsStrongAdmissionAndOriginIsolation(t *testing.T) {
	t.Setenv("API_BASE_URL", "https://nexus.example")
	const resource = "https://nexus.example/documents/export"
	token, claims, err := kernelauth.GenerateTokenForSessionWithAudience(kernelauth.UserSession{ID: "user-current", ProfileId: "profile-current"}, "resource-session", time.Now(), kernelauth.DefaultSessionLifetime, resource)
	if err != nil {
		t.Fatal(err)
	}
	subject := authorizingSubject(t, "user-current", "profile-current")
	repository := &authorizingSessionRepoStub{sessionRepoStub: &sessionRepoStub{}, state: &ports.AuthorizingSessionState{
		Session: models.SystemSession{ID: claims.ID, UserID: subject.UserID(), Token: token, ExpiresAt: time.Now().Add(time.Hour), AuthEpoch: 7},
		Subject: subject, UserAuthorized: true, ConnectedAppAuthorized: true, PermissionFloor: authorizingVersion(t, 24),
	}}
	service := NewAuthService(nil, repository, nil)
	projected, err := service.AdmitRuntimeIdentitySessionForAudience(context.Background(), token, resource)
	if err != nil || projected == nil || projected.SessionID != claims.ID || projected.AuthEpoch != 7 || projected.Subject != subject {
		t.Fatalf("resource projection failed: %v", err)
	}
	if repository.calls != 1 {
		t.Fatalf("resource admission used %d strong reads, want one", repository.calls)
	}
	if result, err := service.AdmitRuntimeIdentitySession(context.Background(), token); result != nil || httpStatus(err) != http.StatusUnauthorized {
		t.Fatal("resource token reached ordinary Runtime admission")
	}
	for _, audience := range []string{"", " ", "https://nexus.example", "https://nexus.example/documents/other"} {
		if result, err := service.AdmitRuntimeIdentitySessionForAudience(context.Background(), token, audience); result != nil || httpStatus(err) != http.StatusUnauthorized {
			t.Fatal("wrong resource admitted")
		}
	}
	repository.state.Session.IsRevoked = true
	if result, err := service.AdmitRuntimeIdentitySessionForAudience(context.Background(), token, resource); result != nil || httpStatus(err) != http.StatusUnauthorized {
		t.Fatal("revoked resource bearer admitted")
	}
}

func TestRuntimeResourceProjectionEmptyAudienceCannotAdmitOriginToken(t *testing.T) {
	t.Setenv("API_BASE_URL", "https://nexus.example")
	token := runtimeAuthorizingToken(t, "origin-session", "user-current", "profile-current", nil)
	subject := authorizingSubject(t, "user-current", "profile-current")
	repository := &authorizingSessionRepoStub{sessionRepoStub: &sessionRepoStub{}, state: &ports.AuthorizingSessionState{
		Session: models.SystemSession{ID: "origin-session", UserID: subject.UserID(), Token: token, ExpiresAt: time.Now().Add(time.Hour), AuthEpoch: 7},
		Subject: subject, UserAuthorized: true, ConnectedAppAuthorized: true, PermissionFloor: authorizingVersion(t, 24),
	}}
	service := NewAuthService(nil, repository, nil)
	if value, err := service.AdmitRuntimeIdentitySession(context.Background(), token); err != nil || value == nil {
		t.Fatalf("ordinary baseline rejected: %v", err)
	}
	before := repository.calls
	if value, err := service.AdmitRuntimeIdentitySessionForAudience(context.Background(), token, ""); value != nil || httpStatus(err) != http.StatusUnauthorized {
		t.Fatal("empty resource policy admitted origin token")
	}
	if repository.calls != before {
		t.Fatal("empty resource policy reached session authority")
	}
}
