package auth

import (
	"context"
	"crypto/sha256"
	"encoding/base64"
	stdErrors "errors"
	"strings"
	"sync"
	"testing"
	"time"

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

const testPublicScope = "external.records.read"

func testPublicPKCEConnectedApp() *models.SystemConnectedApp {
	publicType := OAuthClientTypePublic
	tokenAuth := OAuthTokenAuthNone
	grants := OAuthGrantAuthorizationCode
	scopes := testPublicScope
	redirects := `["https://client.example/callback"]`
	consentPolicy := OAuthConsentPolicyTrusted
	return &models.SystemConnectedApp{
		ID:                      "app-1",
		Name:                    "Public App",
		ConsumerKey:             "client-1",
		RedirectURIs:            &redirects,
		Scopes:                  &scopes,
		IsActive:                true,
		ClientType:              &publicType,
		RequirePkce:             true,
		TokenEndpointAuthMethod: &tokenAuth,
		AllowedGrantTypes:       &grants,
		ConsentPolicy:           &consentPolicy,
	}
}

func TestGenerateAuthCodeForRequestPublicPKCEStoresBoundSnapshot(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	publicType := OAuthClientTypePublic
	tokenAuth := OAuthTokenAuthNone
	grants := OAuthGrantAuthorizationCode
	scopes := testPublicScope
	redirects := `["https://client.example/callback"]`
	consentPolicy := OAuthConsentPolicyTrusted
	app := &models.SystemConnectedApp{
		ID:                      "app-1",
		Name:                    "Public App",
		ConsumerKey:             "client-1",
		RedirectURIs:            &redirects,
		Scopes:                  &scopes,
		IsActive:                true,
		ClientType:              &publicType,
		RequirePkce:             true,
		TokenEndpointAuthMethod: &tokenAuth,
		AllowedGrantTypes:       &grants,
		ConsentPolicy:           &consentPolicy,
	}
	oauthRepo := &oauthRepoStub{}
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, oauthRepo)
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
	verifier, challenge := pkcePair()

	code, err := svc.GenerateAuthCodeForRequest(context.Background(), AuthorizationCodeRequest{
		ClientID:            "client-1",
		RedirectURI:         "https://client.example/callback",
		Scope:               testPublicScope,
		UserID:              "user-1",
		CodeChallenge:       challenge,
		CodeChallengeMethod: OAuthCodeChallengeMethodS256,
		Issuer:              "https://nexus.example",
	})
	if err != nil {
		t.Fatalf("GenerateAuthCodeForRequest: %v verifier=%s", err, verifier)
	}
	if code == "" {
		t.Fatal("GenerateAuthCodeForRequest returned empty code")
	}
	if oauthRepo.stored == nil {
		t.Fatal("StoreAuthCodeRequest was not called")
	}
	if oauthRepo.stored.Code != code {
		t.Fatalf("stored code = %q, want returned code %q", oauthRepo.stored.Code, code)
	}
	if oauthRepo.stored.CodeChallenge != challenge || oauthRepo.stored.CodeChallengeMethod != OAuthCodeChallengeMethodS256 {
		t.Fatalf("stored PKCE snapshot = %q/%q", oauthRepo.stored.CodeChallenge, oauthRepo.stored.CodeChallengeMethod)
	}
	if oauthRepo.stored.ClientTypeAtIssue != OAuthClientTypePublic {
		t.Fatalf("ClientTypeAtIssue = %q, want public", oauthRepo.stored.ClientTypeAtIssue)
	}
	if oauthRepo.stored.Scope != testPublicScope {
		t.Fatalf("stored scope = %q, want %q", oauthRepo.stored.Scope, testPublicScope)
	}
	if oauthRepo.stored.ConsentID != "grant-1" {
		t.Fatalf("stored authorization grant id = %q, want grant-1", oauthRepo.stored.ConsentID)
	}
	if oauthRepo.stored.Issuer != "https://nexus.example" {
		t.Fatalf("stored issuer = %q, want canonical issuer", oauthRepo.stored.Issuer)
	}
}

func TestExchangeTokenForRequestRejectsInactiveAuthorizationGrantBeforeCodeConsume(t *testing.T) {
	clientType := OAuthClientTypeConfidential
	tokenAuth := OAuthTokenAuthClientSecretPost
	grants := OAuthGrantAuthorizationCode
	redirects := `["https://client.example/callback"]`
	app := &models.SystemConnectedApp{
		ID:                      "app-1",
		ConsumerKey:             "client-1",
		ConsumerSecret:          "secret-1",
		RedirectURIs:            &redirects,
		IsActive:                true,
		ClientType:              &clientType,
		TokenEndpointAuthMethod: &tokenAuth,
		AllowedGrantTypes:       &grants,
	}
	repo := &oauthRepoStub{
		grantInactive: true,
		code: &ports.OAuthCodeData{
			ClientID:          "client-1",
			RedirectURI:       "https://client.example/callback",
			UserID:            "user-1",
			ConsentID:         "grant-1",
			ClientTypeAtIssue: OAuthClientTypeConfidential,
		},
	}
	authSvc := NewAuthService(&userRepoStub{user: &models.User{ID: "user-1", IsActive: true}}, &sessionRepoStub{}, nil)
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, authSvc, repo)

	_, err := svc.ExchangeTokenForRequest(context.Background(), TokenExchangeRequest{
		ClientID:     "client-1",
		ClientSecret: "secret-1",
		Code:         "code-1",
		RedirectURI:  "https://client.example/callback",
	})
	if err == nil {
		t.Fatal("ExchangeTokenForRequest succeeded with a revoked authorization grant")
	}
	if repo.codeConsumed {
		t.Fatal("revoked authorization grant consumed the one-time code")
	}
	oauthErr, ok := OAuthProtocolErrorFrom(err)
	if !ok || oauthErr.Code != "invalid_grant" {
		t.Fatalf("error = %#v, want invalid_grant", err)
	}
}

func TestCreateAuthorizeRequestHandlePublicPKCEStoresSnapshot(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	app := testPublicPKCEConnectedApp()
	oauthRepo := &oauthRepoStub{}
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, oauthRepo)
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
	_, challenge := pkcePair()

	result, err := svc.CreateAuthorizeRequestHandle(context.Background(), AuthorizeRequestCreateRequest{
		ClientID:            "client-1",
		RedirectURI:         "https://client.example/callback",
		ResponseType:        "code",
		Scope:               testPublicScope,
		State:               "client-state",
		CodeChallenge:       challenge,
		CodeChallengeMethod: OAuthCodeChallengeMethodS256,
		Issuer:              "https://nexus.example",
	})
	if err != nil {
		t.Fatalf("CreateAuthorizeRequestHandle: %v", err)
	}
	if result == nil || result.Handle == "" {
		t.Fatalf("handle result = %#v, want opaque handle", result)
	}
	if oauthRepo.authorizeStored == nil {
		t.Fatal("StoreAuthorizeRequest was not called")
	}
	if oauthRepo.authorizeStored.Handle != result.Handle {
		t.Fatalf("stored handle = %q, want returned handle", oauthRepo.authorizeStored.Handle)
	}
	if oauthRepo.authorizeStored.ClientID != "client-1" || oauthRepo.authorizeStored.RedirectURI != "https://client.example/callback" {
		t.Fatalf("stored authorize tuple = %#v", oauthRepo.authorizeStored)
	}
	if oauthRepo.authorizeStored.Scope != testPublicScope || oauthRepo.authorizeStored.State != "client-state" {
		t.Fatalf("stored scope/state = %q/%q", oauthRepo.authorizeStored.Scope, oauthRepo.authorizeStored.State)
	}
	if oauthRepo.authorizeStored.CodeChallenge != challenge || oauthRepo.authorizeStored.CodeChallengeMethod != OAuthCodeChallengeMethodS256 {
		t.Fatalf("stored PKCE tuple = %q/%q", oauthRepo.authorizeStored.CodeChallenge, oauthRepo.authorizeStored.CodeChallengeMethod)
	}
}

func TestMCPAuthorizeHandleAndCodePersistOneCanonicalResource(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	app := testPublicPKCEConnectedApp()
	oauthRepo := &oauthRepoStub{}
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, oauthRepo)
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
	_, challenge := pkcePair()
	if _, err := svc.CreateAuthorizeRequestHandle(context.Background(), AuthorizeRequestCreateRequest{
		ClientID: "client-1", RedirectURI: "https://client.example/callback", ResponseType: "code",
		Scope: testPublicScope, CodeChallenge: challenge, CodeChallengeMethod: OAuthCodeChallengeMethodS256,
		Issuer: "https://nexus.example", ResourcePresent: true,
	}); err == nil {
		t.Fatal("explicit empty MCP resource was silently treated as an absent resource")
	} else if oauthErr, ok := OAuthProtocolErrorFrom(err); !ok || oauthErr.Code != "invalid_target" {
		t.Fatalf("explicit empty resource error = %#v, want invalid_target", err)
	}

	handle, err := svc.CreateAuthorizeRequestHandle(context.Background(), AuthorizeRequestCreateRequest{
		ClientID:            "client-1",
		RedirectURI:         "https://client.example/callback",
		ResponseType:        "code",
		Scope:               testPublicScope,
		CodeChallenge:       challenge,
		CodeChallengeMethod: OAuthCodeChallengeMethodS256,
		Issuer:              "https://nexus.example",
		Resource:            "HTTPS://NEXUS.EXAMPLE/mcp",
		ResourcePresent:     true,
	})
	if err != nil || handle == nil {
		t.Fatalf("CreateAuthorizeRequestHandle = %#v, %v", handle, err)
	}
	if oauthRepo.authorizeStored == nil || oauthRepo.authorizeStored.Resource != "https://nexus.example/mcp" {
		t.Fatalf("stored authorize resource = %#v", oauthRepo.authorizeStored)
	}

	if _, err := svc.ResumeAuthorizeRequest(context.Background(), AuthorizeRequestResumeRequest{Handle: handle.Handle, UserID: "user-1"}); err != nil {
		t.Fatalf("ResumeAuthorizeRequest: %v", err)
	}
	if oauthRepo.stored == nil || oauthRepo.stored.Resource != "https://nexus.example/mcp" {
		t.Fatalf("stored code resource = %#v", oauthRepo.stored)
	}
}

func TestMCPTokenExchangeRejectsMissingResourceBeforeCodeConsume(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	app := testPublicPKCEConnectedApp()
	repo := &oauthRepoStub{code: &ports.OAuthCodeData{
		ClientID:          "client-1",
		RedirectURI:       "https://client.example/callback",
		UserID:            "user-1",
		ClientTypeAtIssue: OAuthClientTypePublic,
		Resource:          "https://nexus.example/mcp",
	}}
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, repo)
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})

	_, err := svc.ExchangeTokenForRequest(context.Background(), TokenExchangeRequest{
		ClientID: "client-1", Code: "code-1", RedirectURI: "https://client.example/callback", Issuer: "https://nexus.example",
	})
	if err == nil {
		t.Fatal("ExchangeTokenForRequest accepted a resource-bound code without resource")
	}
	if oauthErr, ok := OAuthProtocolErrorFrom(err); !ok || oauthErr.Code != "invalid_target" {
		t.Fatalf("ExchangeTokenForRequest error = %#v, want invalid_target", err)
	}
	if repo.codeConsumed {
		t.Fatal("resource mismatch consumed the authorization code")
	}
}

func TestMCPAuthorizeRejectsRefreshCapableConnectedApp(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	app := testPublicPKCEConnectedApp()
	app.IssueRefreshTokens = true
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, &oauthRepoStub{})
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
	_, challenge := pkcePair()

	_, err := svc.CreateAuthorizeRequestHandle(context.Background(), AuthorizeRequestCreateRequest{
		ClientID: "client-1", RedirectURI: "https://client.example/callback", ResponseType: "code", Scope: testPublicScope,
		CodeChallenge: challenge, CodeChallengeMethod: OAuthCodeChallengeMethodS256, Issuer: "https://nexus.example", Resource: "https://nexus.example/mcp", ResourcePresent: true,
	})
	if err == nil {
		t.Fatal("CreateAuthorizeRequestHandle accepted a refresh-capable MCP app")
	}
	if oauthErr, ok := OAuthProtocolErrorFrom(err); !ok || oauthErr.Code != "invalid_target" {
		t.Fatalf("CreateAuthorizeRequestHandle error = %#v, want invalid_target", err)
	}
}

func TestMCPAuthorizeRejectsConfidentialOrNonS256Clients(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	_, challenge := pkcePair()
	for _, test := range []struct {
		name   string
		mutate func(*models.SystemConnectedApp)
		method string
	}{
		{
			name: "confidential client",
			mutate: func(app *models.SystemConnectedApp) {
				clientType := OAuthClientTypeConfidential
				tokenAuth := OAuthTokenAuthClientSecretPost
				app.ClientType = &clientType
				app.TokenEndpointAuthMethod = &tokenAuth
			},
			method: OAuthCodeChallengeMethodS256,
		},
		{name: "plain PKCE", mutate: func(*models.SystemConnectedApp) {}, method: "plain"},
	} {
		t.Run(test.name, func(t *testing.T) {
			app := testPublicPKCEConnectedApp()
			test.mutate(app)
			svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, &oauthRepoStub{})
			svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
			_, err := svc.CreateAuthorizeRequestHandle(context.Background(), AuthorizeRequestCreateRequest{
				ClientID: "client-1", RedirectURI: "https://client.example/callback", ResponseType: "code", Scope: testPublicScope,
				CodeChallenge: challenge, CodeChallengeMethod: test.method, Issuer: "https://nexus.example", Resource: "https://nexus.example/mcp", ResourcePresent: true,
			})
			if err == nil {
				t.Fatal("CreateAuthorizeRequestHandle accepted an ineligible MCP client")
			}
		})
	}
}

func TestResumeAuthorizeRequestIssuesCodeAndConsumesHandle(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	app := testPublicPKCEConnectedApp()
	oauthRepo := &oauthRepoStub{}
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, oauthRepo)
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
	_, challenge := pkcePair()

	createResult, err := svc.CreateAuthorizeRequestHandle(context.Background(), AuthorizeRequestCreateRequest{
		ClientID:            "client-1",
		RedirectURI:         "https://client.example/callback",
		ResponseType:        "code",
		Scope:               testPublicScope,
		State:               "client-state",
		CodeChallenge:       challenge,
		CodeChallengeMethod: OAuthCodeChallengeMethodS256,
		Issuer:              "https://nexus.example",
	})
	if err != nil {
		t.Fatalf("CreateAuthorizeRequestHandle: %v", err)
	}

	result, err := svc.ResumeAuthorizeRequest(context.Background(), AuthorizeRequestResumeRequest{
		Handle: createResult.Handle,
		UserID: "user-1",
	})
	if err != nil {
		t.Fatalf("ResumeAuthorizeRequest: %v", err)
	}
	if result.Code == "" || result.RedirectURI != "https://client.example/callback" || result.State != "client-state" {
		t.Fatalf("resume result = %#v", result)
	}
	if !oauthRepo.consumeAndStoreHit || oauthRepo.stored == nil {
		t.Fatal("ConsumeAuthorizeRequestAndStoreAuthCode did not store the issued code")
	}
	if oauthRepo.stored.UserID != "user-1" || oauthRepo.stored.Scope != testPublicScope || oauthRepo.stored.ClientTypeAtIssue != OAuthClientTypePublic {
		t.Fatalf("stored auth code request = %#v", oauthRepo.stored)
	}

	replay, err := svc.ResumeAuthorizeRequest(context.Background(), AuthorizeRequestResumeRequest{
		Handle: createResult.Handle,
		UserID: "user-1",
	})
	if err == nil || replay != nil {
		t.Fatalf("replay result=%#v err=%v, want invalid request", replay, err)
	}
	oauthErr, ok := OAuthProtocolErrorFrom(err)
	if !ok || oauthErr.Code != "invalid_request" {
		t.Fatalf("replay error = %#v, want invalid_request", err)
	}
}

func TestResumeAuthorizeRequestConsumesHandleOnCurrentPolicyDenial(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	app := testPublicPKCEConnectedApp()
	adminApproved := "AdminApproved"
	app.PermittedUsers = &adminApproved
	oauthRepo := &oauthRepoStub{}
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, oauthRepo)
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
	_, challenge := pkcePair()

	createResult, err := svc.CreateAuthorizeRequestHandle(context.Background(), AuthorizeRequestCreateRequest{
		ClientID:            "client-1",
		RedirectURI:         "https://client.example/callback",
		ResponseType:        "code",
		Scope:               testPublicScope,
		State:               "client-state",
		CodeChallenge:       challenge,
		CodeChallengeMethod: OAuthCodeChallengeMethodS256,
		Issuer:              "https://nexus.example",
	})
	if err != nil {
		t.Fatalf("CreateAuthorizeRequestHandle: %v", err)
	}

	svc = NewOAuthService(nil, &systemRepoStub{app: app, permitted: false}, nil, nil, oauthRepo)
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
	result, err := svc.ResumeAuthorizeRequest(context.Background(), AuthorizeRequestResumeRequest{
		Handle: createResult.Handle,
		UserID: "user-1",
	})
	if err == nil {
		t.Fatal("ResumeAuthorizeRequest succeeded after app access denial")
	}
	if result == nil || result.RedirectURI != "https://client.example/callback" || result.State != "client-state" {
		t.Fatalf("resume denial result = %#v, want trusted callback context", result)
	}
	if !oauthRepo.authorizeConsumed {
		t.Fatal("policy denial did not consume the authorize handle")
	}
	if oauthRepo.consumeAndStoreHit {
		t.Fatal("policy denial should not store an auth code")
	}
	oauthErr, ok := OAuthProtocolErrorFrom(err)
	if !ok || oauthErr.Code != "access_denied" {
		t.Fatalf("error = %#v, want access_denied", err)
	}
}

func TestResumeAuthorizeRequestPreservesHandleForRuntimeHiddenConnectedApp(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	app := testPublicPKCEConnectedApp()
	oauthRepo := &oauthRepoStub{}
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, oauthRepo)
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
	_, challenge := pkcePair()

	createResult, err := svc.CreateAuthorizeRequestHandle(context.Background(), AuthorizeRequestCreateRequest{
		ClientID:            "client-1",
		RedirectURI:         "https://client.example/callback",
		ResponseType:        "code",
		Scope:               testPublicScope,
		State:               "client-state",
		CodeChallenge:       challenge,
		CodeChallengeMethod: OAuthCodeChallengeMethodS256,
		Issuer:              "https://nexus.example",
	})
	if err != nil {
		t.Fatalf("CreateAuthorizeRequestHandle: %v", err)
	}

	staleRuntimeSvc := NewOAuthService(nil, &systemRepoStub{err: errors.NewNotFoundError("connected app", "client-1")}, nil, nil, oauthRepo)
	staleRuntimeSvc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
	result, err := staleRuntimeSvc.ResumeAuthorizeRequest(context.Background(), AuthorizeRequestResumeRequest{
		Handle: createResult.Handle,
		UserID: "user-1",
	})
	if err == nil {
		t.Fatal("ResumeAuthorizeRequest succeeded on runtime-hidden Connected App")
	}
	if result == nil || result.RedirectURI != "https://client.example/callback" || result.State != "client-state" {
		t.Fatalf("hidden-client resume result = %#v, want trusted callback context", result)
	}
	oauthErr, ok := OAuthProtocolErrorFrom(err)
	if !ok || oauthErr.Code != "invalid_client" {
		t.Fatalf("hidden-client resume error = %#v, want invalid_client", err)
	}
	if oauthRepo.authorizeConsumed {
		t.Fatal("runtime-hidden Connected App consumed the authorize handle")
	}
	if oauthRepo.consumeAndStoreHit {
		t.Fatal("runtime-hidden Connected App should not store an auth code")
	}

	freshRuntimeSvc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, oauthRepo)
	freshRuntimeSvc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
	retry, err := freshRuntimeSvc.ResumeAuthorizeRequest(context.Background(), AuthorizeRequestResumeRequest{
		Handle: createResult.Handle,
		UserID: "user-1",
	})
	if err != nil {
		t.Fatalf("retry ResumeAuthorizeRequest after hidden-client miss: %v", err)
	}
	if retry == nil || retry.Code == "" || retry.RedirectURI != "https://client.example/callback" {
		t.Fatalf("retry result = %#v, want issued code", retry)
	}
}

func TestResumeAuthorizeRequestPreservesHandleWhenAtomicIssueFails(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	app := testPublicPKCEConnectedApp()
	oauthRepo := &oauthRepoStub{consumeAndStoreErr: stdErrors.New("temporary code storage failure")}
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, oauthRepo)
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
	_, challenge := pkcePair()

	createResult, err := svc.CreateAuthorizeRequestHandle(context.Background(), AuthorizeRequestCreateRequest{
		ClientID:            "client-1",
		RedirectURI:         "https://client.example/callback",
		ResponseType:        "code",
		Scope:               testPublicScope,
		State:               "client-state",
		CodeChallenge:       challenge,
		CodeChallengeMethod: OAuthCodeChallengeMethodS256,
		Issuer:              "https://nexus.example",
	})
	if err != nil {
		t.Fatalf("CreateAuthorizeRequestHandle: %v", err)
	}

	result, err := svc.ResumeAuthorizeRequest(context.Background(), AuthorizeRequestResumeRequest{
		Handle: createResult.Handle,
		UserID: "user-1",
	})
	if err == nil {
		t.Fatal("ResumeAuthorizeRequest succeeded despite atomic issue failure")
	}
	if result == nil || result.RedirectURI != "https://client.example/callback" || result.State != "client-state" {
		t.Fatalf("failed resume result = %#v, want trusted callback context", result)
	}
	if oauthRepo.authorizeConsumed {
		t.Fatal("atomic issue failure consumed the authorize handle")
	}

	oauthRepo.consumeAndStoreErr = nil
	retry, err := svc.ResumeAuthorizeRequest(context.Background(), AuthorizeRequestResumeRequest{
		Handle: createResult.Handle,
		UserID: "user-1",
	})
	if err != nil {
		t.Fatalf("retry ResumeAuthorizeRequest: %v", err)
	}
	if retry == nil || retry.Code == "" {
		t.Fatalf("retry result = %#v, want issued code", retry)
	}
}

func TestResolveOAuthPolicyPublicAuthorizeFailsBeforeCodeStoreWhenFlagDisabled(t *testing.T) {
	publicType := OAuthClientTypePublic
	tokenAuth := OAuthTokenAuthNone
	grants := OAuthGrantAuthorizationCode
	scopes := testPublicScope
	redirects := `["https://client.example/callback"]`
	consentPolicy := OAuthConsentPolicyTrusted
	app := &models.SystemConnectedApp{
		ID:                      "app-1",
		ConsumerKey:             "client-1",
		RedirectURIs:            &redirects,
		Scopes:                  &scopes,
		IsActive:                true,
		ClientType:              &publicType,
		RequirePkce:             true,
		TokenEndpointAuthMethod: &tokenAuth,
		AllowedGrantTypes:       &grants,
		ConsentPolicy:           &consentPolicy,
	}
	oauthRepo := &oauthRepoStub{}
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, oauthRepo)
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
	_, challenge := pkcePair()

	_, err := svc.ResolveOAuthPolicy(context.Background(), OAuthPolicyRequest{
		UseCase:             OAuthPolicyAuthorizeCodeIssue,
		ClientID:            "client-1",
		GrantType:           OAuthGrantAuthorizationCode,
		ResponseType:        "code",
		RedirectURI:         "https://client.example/callback",
		RequestedScope:      testPublicScope,
		CodeChallenge:       challenge,
		CodeChallengeMethod: OAuthCodeChallengeMethodS256,
		Issuer:              "https://nexus.example",
	})
	if err == nil {
		t.Fatal("ResolveOAuthPolicy without rollout flag succeeded, want error")
	}
	if oauthRepo.stored != nil {
		t.Fatalf("StoreAuthCodeRequest ran during policy resolution: %#v", oauthRepo.stored)
	}
}

func TestResolveOAuthPolicyConnectedAppGrantDecisions(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	publicType := OAuthClientTypePublic
	confidentialType := OAuthClientTypeConfidential
	publicTokenAuth := OAuthTokenAuthNone
	confidentialTokenAuth := OAuthTokenAuthClientSecretPost
	authCodeGrant := OAuthGrantAuthorizationCode
	allGrants := "authorization_code,password,client_credentials"
	publicScopes := testPublicScope
	confidentialScopes := "api refresh_token"
	redirects := `["https://client.example/callback"]`
	trustedConsent := OAuthConsentPolicyTrusted
	secret := "client-secret"
	secretHash, err := pkgauth.HashPassword(secret)
	if err != nil {
		t.Fatalf("HashPassword: %v", err)
	}
	_, challenge := pkcePair()

	publicApp := &models.SystemConnectedApp{
		ID:                      "public-app",
		ConsumerKey:             "public-client",
		RedirectURIs:            &redirects,
		Scopes:                  &publicScopes,
		IsActive:                true,
		ClientType:              &publicType,
		RequirePkce:             true,
		TokenEndpointAuthMethod: &publicTokenAuth,
		AllowedGrantTypes:       &authCodeGrant,
		ConsentPolicy:           &trustedConsent,
	}
	confidentialApp := &models.SystemConnectedApp{
		ID:                      "confidential-app",
		ConsumerKey:             "confidential-client",
		ConsumerSecretHash:      &secretHash,
		RedirectURIs:            &redirects,
		Scopes:                  &confidentialScopes,
		IsActive:                true,
		ClientType:              &confidentialType,
		TokenEndpointAuthMethod: &confidentialTokenAuth,
		AllowedGrantTypes:       &allGrants,
	}

	tests := []struct {
		name             string
		app              *models.SystemConnectedApp
		req              OAuthPolicyRequest
		publicScopes     PublicScopePolicyProvider
		wantErrCode      string
		wantClientType   string
		wantTokenAuth    string
		wantScope        string
		wantRequiresPKCE bool
	}{
		{
			name: "public auth-code authorize resolves trusted PKCE policy",
			app:  publicApp,
			req: OAuthPolicyRequest{
				UseCase:             OAuthPolicyAuthorizeCodeIssue,
				ClientID:            "public-client",
				ResponseType:        "code",
				RedirectURI:         "https://client.example/callback",
				RequestedScope:      testPublicScope,
				CodeChallenge:       challenge,
				CodeChallengeMethod: OAuthCodeChallengeMethodS256,
				Issuer:              "https://nexus.example",
			},
			publicScopes:     publicScopeProviderStub{testPublicScope: true},
			wantClientType:   OAuthClientTypePublic,
			wantTokenAuth:    OAuthTokenAuthNone,
			wantScope:        testPublicScope,
			wantRequiresPKCE: true,
		},
		{
			name: "confidential auth-code authorize resolves redirect and scope",
			app:  confidentialApp,
			req: OAuthPolicyRequest{
				UseCase:        OAuthPolicyAuthorizeCodeIssue,
				ClientID:       "confidential-client",
				ResponseType:   "code",
				RedirectURI:    "https://client.example/callback",
				RequestedScope: "api",
			},
			wantClientType: OAuthClientTypeConfidential,
			wantTokenAuth:  OAuthTokenAuthClientSecretPost,
			wantScope:      "api",
		},
		{
			name: "public auth-code token resolves issuer and scope",
			app:  publicApp,
			req: OAuthPolicyRequest{
				UseCase:        OAuthPolicyAuthorizeCodeToken,
				ClientID:       "public-client",
				RedirectURI:    "https://client.example/callback",
				RequestedScope: testPublicScope,
				Issuer:         "https://nexus.example",
			},
			publicScopes:     publicScopeProviderStub{testPublicScope: true},
			wantClientType:   OAuthClientTypePublic,
			wantTokenAuth:    OAuthTokenAuthNone,
			wantScope:        testPublicScope,
			wantRequiresPKCE: true,
		},
		{
			name: "confidential auth-code token validates secret and scope",
			app:  confidentialApp,
			req: OAuthPolicyRequest{
				UseCase:         OAuthPolicyAuthorizeCodeToken,
				ClientID:        "confidential-client",
				RedirectURI:     "https://client.example/callback",
				RequestedScope:  "api",
				PresentedSecret: secret,
			},
			wantClientType: OAuthClientTypeConfidential,
			wantTokenAuth:  OAuthTokenAuthClientSecretPost,
			wantScope:      "api",
		},
		{
			name: "confidential password grant validates secret and scope",
			app:  confidentialApp,
			req: OAuthPolicyRequest{
				UseCase:         OAuthPolicyPasswordGrant,
				ClientID:        "confidential-client",
				RequestedScope:  "api",
				PresentedSecret: secret,
			},
			wantClientType: OAuthClientTypeConfidential,
			wantTokenAuth:  OAuthTokenAuthClientSecretPost,
			wantScope:      "api",
		},
		{
			name: "confidential client-credentials grant resolves app scopes",
			app:  confidentialApp,
			req: OAuthPolicyRequest{
				UseCase:         OAuthPolicyClientCredentials,
				ClientID:        "confidential-client",
				PresentedSecret: secret,
			},
			wantClientType: OAuthClientTypeConfidential,
			wantTokenAuth:  OAuthTokenAuthClientSecretPost,
			wantScope:      confidentialScopes,
		},
		{
			name: "public password grant fails closed",
			app:  publicApp,
			req: OAuthPolicyRequest{
				UseCase:  OAuthPolicyPasswordGrant,
				ClientID: "public-client",
			},
			wantErrCode: "invalid_client",
		},
		{
			name: "public client-credentials grant fails closed",
			app:  publicApp,
			req: OAuthPolicyRequest{
				UseCase:  OAuthPolicyClientCredentials,
				ClientID: "public-client",
			},
			wantErrCode: "invalid_client",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			svc := NewOAuthService(nil, &systemRepoStub{app: tt.app, permitted: true}, nil, nil, &oauthRepoStub{})
			if tt.publicScopes != nil {
				svc.SetPublicScopePolicyProvider(tt.publicScopes)
			}

			decision, err := svc.ResolveOAuthPolicy(context.Background(), tt.req)
			if tt.wantErrCode != "" {
				if err == nil {
					t.Fatalf("ResolveOAuthPolicy succeeded, want %s", tt.wantErrCode)
				}
				oauthErr, ok := OAuthProtocolErrorFrom(err)
				if !ok || oauthErr.Code != tt.wantErrCode {
					t.Fatalf("error = %#v, want OAuth error %q", err, tt.wantErrCode)
				}
				return
			}
			if err != nil {
				t.Fatalf("ResolveOAuthPolicy: %v", err)
			}
			if decision.ClientType != tt.wantClientType {
				t.Fatalf("ClientType = %q, want %q", decision.ClientType, tt.wantClientType)
			}
			if decision.TokenAuthMethod != tt.wantTokenAuth {
				t.Fatalf("TokenAuthMethod = %q, want %q", decision.TokenAuthMethod, tt.wantTokenAuth)
			}
			if decision.EffectiveScope != tt.wantScope {
				t.Fatalf("EffectiveScope = %q, want %q", decision.EffectiveScope, tt.wantScope)
			}
			if decision.RequiresPKCE != tt.wantRequiresPKCE {
				t.Fatalf("RequiresPKCE = %v, want %v", decision.RequiresPKCE, tt.wantRequiresPKCE)
			}
			if tt.wantClientType == OAuthClientTypePublic && decision.PublicIssuer != "https://nexus.example" {
				t.Fatalf("PublicIssuer = %q, want canonical issuer", decision.PublicIssuer)
			}
		})
	}
}

func TestExchangeTokenForRequestPublicAuthorizationCodeVerifiesS256PKCE(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	publicType := OAuthClientTypePublic
	tokenAuth := OAuthTokenAuthNone
	grants := OAuthGrantAuthorizationCode
	scopes := testPublicScope
	redirects := `["https://client.example/callback"]`
	consentPolicy := OAuthConsentPolicyTrusted
	app := &models.SystemConnectedApp{
		ID:                      "app-1",
		ConsumerKey:             "client-1",
		RedirectURIs:            &redirects,
		Scopes:                  &scopes,
		IsActive:                true,
		ClientType:              &publicType,
		RequirePkce:             true,
		TokenEndpointAuthMethod: &tokenAuth,
		AllowedGrantTypes:       &grants,
		ConsentPolicy:           &consentPolicy,
	}
	verifier, challenge := pkcePair()
	codeData := &ports.OAuthCodeData{
		ClientID:            "client-1",
		RedirectURI:         "https://client.example/callback",
		Scope:               testPublicScope,
		UserID:              "user-1",
		CodeChallenge:       challenge,
		CodeChallengeMethod: OAuthCodeChallengeMethodS256,
		Issuer:              "https://nexus.example",
		ClientTypeAtIssue:   OAuthClientTypePublic,
	}

	tests := []struct {
		name         string
		codeVerifier string
		wantErrCode  string
	}{
		{name: "matching verifier issues token", codeVerifier: verifier},
		{name: "wrong verifier fails closed", codeVerifier: verifier + "x", wantErrCode: "invalid_grant"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			authSvc := NewAuthService(&userRepoStub{
				user: &models.User{
					ID:        "user-1",
					Username:  "ada@example.com",
					Email:     "ada@example.com",
					FirstName: "Ada",
					LastName:  "Lovelace",
					ProfileID: "profile-1",
					IsActive:  true,
				},
			}, &sessionRepoStub{}, nil)
			svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, authSvc, &oauthRepoStub{code: codeData})
			svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})

			token, err := svc.ExchangeTokenForRequest(context.Background(), TokenExchangeRequest{
				ClientID:     "client-1",
				Code:         "code-1",
				RedirectURI:  "https://client.example/callback",
				CodeVerifier: tt.codeVerifier,
				Issuer:       "https://nexus.example",
			})
			if tt.wantErrCode != "" {
				if err == nil {
					t.Fatalf("ExchangeTokenForRequest succeeded, want %s", tt.wantErrCode)
				}
				oauthErr, ok := OAuthProtocolErrorFrom(err)
				if !ok || oauthErr.Code != tt.wantErrCode {
					t.Fatalf("error = %#v, want OAuth error %q", err, tt.wantErrCode)
				}
				return
			}
			if err != nil {
				t.Fatalf("ExchangeTokenForRequest: %v", err)
			}
			if token.Scope != testPublicScope {
				t.Fatalf("token.Scope = %q, want %q", token.Scope, testPublicScope)
			}
			if token.InstanceURL != "https://nexus.example" {
				t.Fatalf("token.InstanceURL = %q, want canonical public issuer", token.InstanceURL)
			}
			if token.ID != "" {
				t.Fatalf("app-blind token.ID = %q, want empty root identity URL", token.ID)
			}
			if token.IssuedAt == "" {
				t.Fatal("token.IssuedAt is empty, want generic issued timestamp")
			}
			if token.Signature != "" {
				t.Fatalf("app-blind token signature = %q, want empty", token.Signature)
			}
		})
	}
}

func TestMCPTokenExchangeMintsExactAudienceAndNoRefreshToken(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	app := testPublicPKCEConnectedApp()
	verifier, challenge := pkcePair()
	repo := &oauthRepoStub{code: &ports.OAuthCodeData{
		ClientID: "client-1", RedirectURI: "https://client.example/callback", Scope: testPublicScope, UserID: "user-1",
		CodeChallenge: challenge, CodeChallengeMethod: OAuthCodeChallengeMethodS256, Issuer: "https://nexus.example",
		ClientTypeAtIssue: OAuthClientTypePublic, Resource: "https://nexus.example/mcp",
	}}
	authSvc := NewAuthService(&userRepoStub{user: &models.User{
		ID: "user-1", Username: "ada@example.com", Email: "ada@example.com", ProfileID: "profile-1", IsActive: true,
	}}, &sessionRepoStub{}, nil)
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, authSvc, repo)
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})

	token, err := svc.ExchangeTokenForRequest(context.Background(), TokenExchangeRequest{
		ClientID: "client-1", Code: "code-1", RedirectURI: "https://client.example/callback", CodeVerifier: verifier,
		Issuer: "https://nexus.example", Resource: "https://nexus.example/mcp", ResourcePresent: true,
	})
	if err != nil {
		t.Fatalf("ExchangeTokenForRequest: %v", err)
	}
	if token.RefreshToken != "" {
		t.Fatalf("MCP token response issued refresh token %q", token.RefreshToken)
	}
	if _, err := pkgauth.ValidateTokenForAudience(token.AccessToken, "https://nexus.example/mcp"); err != nil {
		t.Fatalf("MCP access token is not exact-audience bound: %v", err)
	}
}

func TestExchangeTokenForRequestGrandfathersUnboundPublicCodeAfterConsentCutover(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	app := testPublicPKCEConnectedApp()
	prompt := OAuthConsentPolicyPrompt
	app.ConsentPolicy = &prompt
	verifier, challenge := pkcePair()
	repo := &oauthRepoStub{code: &ports.OAuthCodeData{
		ClientID:            "client-1",
		RedirectURI:         "https://client.example/callback",
		Scope:               testPublicScope,
		UserID:              "user-1",
		CodeChallenge:       challenge,
		CodeChallengeMethod: OAuthCodeChallengeMethodS256,
		Issuer:              "https://nexus.example",
		ClientTypeAtIssue:   OAuthClientTypePublic,
		// No ConsentID: this is a pre-cutover authorization code.
	}}
	authSvc := NewAuthService(&userRepoStub{user: &models.User{
		ID: "user-1", Username: "ada@example.com", Email: "ada@example.com", IsActive: true,
	}}, &sessionRepoStub{}, nil)
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, authSvc, repo)
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})

	if _, err := svc.ExchangeTokenForRequest(context.Background(), TokenExchangeRequest{
		ClientID: "client-1", Code: "pre-cutover-code", RedirectURI: "https://client.example/callback",
		CodeVerifier: verifier, Issuer: "https://nexus.example",
	}); err != nil {
		t.Fatalf("ExchangeTokenForRequest grandfathered code: %v", err)
	}
	if !repo.codeConsumed {
		t.Fatal("grandfathered code was not consumed after issuing the token")
	}
}

func TestExchangeTokenForRequestSerializesCredentialIssuanceWithAccessRevocation(t *testing.T) {
	permittedUsers := "AdminApproved"
	clientType := OAuthClientTypeConfidential
	tokenAuth := OAuthTokenAuthClientSecretPost
	grants := OAuthGrantAuthorizationCode + " " + OAuthGrantRefreshToken
	redirects := `["https://client.example/callback"]`
	state := newOAuthCredentialRevocationRaceState()
	app := &models.SystemConnectedApp{
		ID:                      "app-1",
		ConsumerKey:             "client-1",
		ConsumerSecret:          "secret-1",
		RedirectURIs:            &redirects,
		IsActive:                true,
		ClientType:              &clientType,
		TokenEndpointAuthMethod: &tokenAuth,
		AllowedGrantTypes:       &grants,
		IssueRefreshTokens:      true,
		PermittedUsers:          &permittedUsers,
	}
	code := &ports.OAuthCodeData{
		ClientID:          "client-1",
		RedirectURI:       "https://client.example/callback",
		UserID:            "user-1",
		ClientTypeAtIssue: OAuthClientTypeConfidential,
	}
	systemRepo := &oauthCredentialRaceSystemRepo{
		systemRepoStub: &systemRepoStub{app: app, permitted: true},
		state:          state,
	}
	sessionRepo := &oauthCredentialRaceSessionRepo{
		sessionRepoStub: &sessionRepoStub{},
		state:           state,
	}
	oauthRepo := &oauthCredentialRaceOAuthRepo{
		oauthRepoStub: &oauthRepoStub{code: code},
		state:         state,
	}
	authSvc := NewAuthService(&userRepoStub{
		user: &models.User{
			ID:        "user-1",
			Username:  "ada@example.com",
			Email:     "ada@example.com",
			FirstName: "Ada",
			LastName:  "Lovelace",
			ProfileID: "profile-1",
			IsActive:  true,
		},
	}, sessionRepo, nil)
	svc := NewOAuthService(nil, systemRepo, sessionRepo, authSvc, oauthRepo)

	go state.revokeConnectedAppCredentials()
	_, err := svc.ExchangeTokenForRequest(context.Background(), TokenExchangeRequest{
		ClientID:     "client-1",
		ClientSecret: "secret-1",
		Code:         "code-1",
		RedirectURI:  "https://client.example/callback",
	})
	if err != nil {
		t.Fatalf("ExchangeTokenForRequest: %v", err)
	}
	<-state.revocationDone

	state.mu.Lock()
	defer state.mu.Unlock()
	if state.activeSession {
		t.Fatal("authorization-code exchange left an active session after Connected App access revocation")
	}
	if state.activeRefreshToken {
		t.Fatal("authorization-code exchange left an active refresh token after Connected App access revocation")
	}
}

func TestResolveOAuthPolicyAuthorizeRequiresResponseTypeCode(t *testing.T) {
	confidentialType := OAuthClientTypeConfidential
	tokenAuth := OAuthTokenAuthClientSecretPost
	grants := OAuthGrantAuthorizationCode
	redirects := `["https://client.example/callback"]`
	app := &models.SystemConnectedApp{
		ID:                      "app-1",
		ConsumerKey:             "client-1",
		RedirectURIs:            &redirects,
		IsActive:                true,
		ClientType:              &confidentialType,
		TokenEndpointAuthMethod: &tokenAuth,
		AllowedGrantTypes:       &grants,
	}
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, &oauthRepoStub{})

	_, err := svc.ResolveOAuthPolicy(context.Background(), OAuthPolicyRequest{
		UseCase:     OAuthPolicyAuthorizeCodeIssue,
		ClientID:    "client-1",
		RedirectURI: "https://client.example/callback",
	})
	if err == nil {
		t.Fatal("ResolveOAuthPolicy accepted missing response_type, want unsupported_response_type")
	}
	oauthErr, ok := OAuthProtocolErrorFrom(err)
	if !ok || oauthErr.Code != "unsupported_response_type" {
		t.Fatalf("error = %#v, want unsupported_response_type", err)
	}
}

func TestGenerateAuthCodeForRequestPublicPKCERequiresRolloutFlagAndTrustedPolicy(t *testing.T) {
	publicType := OAuthClientTypePublic
	tokenAuth := OAuthTokenAuthNone
	grants := OAuthGrantAuthorizationCode
	scopes := testPublicScope
	redirects := `["https://client.example/callback"]`
	prompt := OAuthConsentPolicyPrompt
	app := &models.SystemConnectedApp{
		ID:                      "app-1",
		ConsumerKey:             "client-1",
		RedirectURIs:            &redirects,
		Scopes:                  &scopes,
		IsActive:                true,
		ClientType:              &publicType,
		RequirePkce:             true,
		TokenEndpointAuthMethod: &tokenAuth,
		AllowedGrantTypes:       &grants,
		ConsentPolicy:           &prompt,
	}
	_, challenge := pkcePair()

	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, &oauthRepoStub{})
	svc.SetPublicScopePolicyProvider(publicScopeProviderStub{testPublicScope: true})
	_, err := svc.GenerateAuthCodeForRequest(context.Background(), AuthorizationCodeRequest{
		ClientID:            "client-1",
		RedirectURI:         "https://client.example/callback",
		Scope:               testPublicScope,
		UserID:              "user-1",
		CodeChallenge:       challenge,
		CodeChallengeMethod: OAuthCodeChallengeMethodS256,
	})
	if err == nil {
		t.Fatal("public app issued code without rollout flag, want error")
	}

	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")
	_, err = svc.GenerateAuthCodeForRequest(context.Background(), AuthorizationCodeRequest{
		ClientID:            "client-1",
		RedirectURI:         "https://client.example/callback",
		Scope:               testPublicScope,
		UserID:              "user-1",
		CodeChallenge:       challenge,
		CodeChallengeMethod: OAuthCodeChallengeMethodS256,
		Issuer:              "https://nexus.example",
	})
	if err == nil {
		t.Fatal("prompt-consent public app issued code before consent engine exists, want error")
	}
}

func TestExchangeTokenForRequestPublicRejectsIssuerMismatch(t *testing.T) {
	t.Setenv(publicOAuthPKCEEnabledEnv, "true")
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	publicType := OAuthClientTypePublic
	tokenAuth := OAuthTokenAuthNone
	grants := OAuthGrantAuthorizationCode
	scopes := testPublicScope
	redirects := `["https://client.example/callback"]`
	consentPolicy := OAuthConsentPolicyTrusted
	app := &models.SystemConnectedApp{
		ID:                      "app-1",
		ConsumerKey:             "client-1",
		RedirectURIs:            &redirects,
		Scopes:                  &scopes,
		IsActive:                true,
		ClientType:              &publicType,
		RequirePkce:             true,
		TokenEndpointAuthMethod: &tokenAuth,
		AllowedGrantTypes:       &grants,
		ConsentPolicy:           &consentPolicy,
	}
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, nil, &oauthRepoStub{
		code: &ports.OAuthCodeData{
			ClientID:          "client-1",
			RedirectURI:       "https://client.example/callback",
			Scope:             testPublicScope,
			UserID:            "user-1",
			Issuer:            "https://evil.example",
			ClientTypeAtIssue: OAuthClientTypePublic,
		},
	})

	_, err := svc.ExchangeTokenForRequest(context.Background(), TokenExchangeRequest{
		ClientID:    "client-1",
		Code:        "code-1",
		RedirectURI: "https://client.example/callback",
		Issuer:      "https://nexus.example",
	})
	if err == nil {
		t.Fatal("ExchangeTokenForRequest succeeded with mismatched issuer, want error")
	}
}

func TestExchangeTokenForRequestAuthCodeAppliesTokenResponseProfile(t *testing.T) {
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	confidentialType := OAuthClientTypeConfidential
	tokenAuth := OAuthTokenAuthClientSecretPost
	grants := OAuthGrantAuthorizationCode
	scopes := "api"
	redirects := `["https://client.example/callback"]`
	secret := "client-secret"
	sessionClass := "salesforce_compat"
	secretHash, err := pkgauth.HashPassword(secret)
	if err != nil {
		t.Fatalf("HashPassword: %v", err)
	}
	app := &models.SystemConnectedApp{
		ID:                      "app-1",
		ConsumerKey:             "client-1",
		ConsumerSecretHash:      &secretHash,
		RedirectURIs:            &redirects,
		Scopes:                  &scopes,
		IsActive:                true,
		ClientType:              &confidentialType,
		TokenEndpointAuthMethod: &tokenAuth,
		AllowedGrantTypes:       &grants,
		SessionIssuerClass:      &sessionClass,
	}
	sessionRepo := &sessionRepoStub{}
	authSvc := NewAuthService(&userRepoStub{
		user: &models.User{
			ID:        "user-1",
			Username:  "ada@example.com",
			Email:     "ada@example.com",
			FirstName: "Ada",
			LastName:  "Lovelace",
			ProfileID: "profile-1",
			IsActive:  true,
		},
	}, sessionRepo, nil)
	oauthRepo := &oauthRepoStub{
		code: &ports.OAuthCodeData{
			ClientID:          "client-1",
			RedirectURI:       "https://client.example/callback",
			Scope:             scopes,
			UserID:            "user-1",
			ClientTypeAtIssue: OAuthClientTypeConfidential,
		},
	}
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, authSvc, oauthRepo)
	svc.SetTokenResponseProfiles([]TokenResponseProfileRegistration{testTokenResponseProfile()})

	token, err := svc.ExchangeTokenForRequest(context.Background(), TokenExchangeRequest{
		ClientID:     "client-1",
		ClientSecret: secret,
		Code:         "code-1",
		RedirectURI:  "https://client.example/callback",
	})
	if err != nil {
		t.Fatalf("ExchangeTokenForRequest: %v", err)
	}
	wantID := "https://nexus.example/id/00D000000000001/user-1"
	if token.InstanceURL != "https://nexus.example" {
		t.Fatalf("token.InstanceURL = %q, want generic canonical issuer", token.InstanceURL)
	}
	if token.ID != wantID {
		t.Fatalf("token.ID = %q, want %q", token.ID, wantID)
	}
	if token.IssuedAt == "" {
		t.Fatal("token.IssuedAt is empty, want profile-issued timestamp")
	}
	if want := secret + ":" + token.IssuedAt; token.Signature != want {
		t.Fatalf("token.Signature = %q, want product profile signature %q", token.Signature, want)
	}
	claims, err := pkgauth.ValidateToken(token.AccessToken)
	if err != nil {
		t.Fatalf("ValidateToken: %v", err)
	}
	if got := claims.User.SessionClass; got != sessionClass {
		t.Fatalf("claims.User.SessionClass = %q, want %q", got, sessionClass)
	}
}

func TestApplyTokenResponseProfilesRejectsMultipleMatchingProfiles(t *testing.T) {
	token := &models.SessionToken{
		InstanceURL: "https://nexus.example",
	}
	otherProfile := testTokenResponseProfile()
	otherProfile.ProfileID = "other-compatible"

	err := applyTokenResponseProfiles(token, TokenResponseProfileContext{
		InstanceURL:    "https://nexus.example",
		UserID:         "user-1",
		GrantType:      OAuthGrantAuthorizationCode,
		IssuedAt:       "1710000000000",
		ClientSecret:   "client-secret",
		ConnectedAppID: "app-1",
		SessionClass:   "salesforce_compat",
	}, []TokenResponseProfileRegistration{testTokenResponseProfile(), otherProfile})
	if err == nil {
		t.Fatal("applyTokenResponseProfiles succeeded, want conflict error")
	}
	oauthErr, ok := OAuthProtocolErrorFrom(err)
	if !ok {
		t.Fatalf("applyTokenResponseProfiles error = %T %[1]v, want OAuthProtocolError", err)
	}
	if oauthErr.Code != "server_error" || oauthErr.Reason != "token_response_profile_conflict" {
		t.Fatalf("OAuthProtocolError = %#v, want token_response_profile_conflict server_error", oauthErr)
	}
	if token.ID != "" || token.IssuedAt != "" || token.Signature != "" {
		t.Fatalf("direct apply conflict should not mutate empty token: %#v", token)
	}
}

func TestExchangeTokenForRequestAuthCodeRejectsOverlappingTokenResponseProfiles(t *testing.T) {
	t.Setenv(publicOAuthIssuerEnv, "https://nexus.example")

	confidentialType := OAuthClientTypeConfidential
	tokenAuth := OAuthTokenAuthClientSecretPost
	grants := OAuthGrantAuthorizationCode
	scopes := "api"
	redirects := `["https://client.example/callback"]`
	secret := "client-secret"
	sessionClass := "salesforce_compat"
	secretHash, err := pkgauth.HashPassword(secret)
	if err != nil {
		t.Fatalf("HashPassword: %v", err)
	}
	app := &models.SystemConnectedApp{
		ID:                      "app-1",
		ConsumerKey:             "client-1",
		ConsumerSecretHash:      &secretHash,
		RedirectURIs:            &redirects,
		Scopes:                  &scopes,
		IsActive:                true,
		ClientType:              &confidentialType,
		TokenEndpointAuthMethod: &tokenAuth,
		AllowedGrantTypes:       &grants,
		SessionIssuerClass:      &sessionClass,
	}
	sessionRepo := &sessionRepoStub{}
	authSvc := NewAuthService(&userRepoStub{
		user: &models.User{
			ID:        "user-1",
			Username:  "ada@example.com",
			Email:     "ada@example.com",
			FirstName: "Ada",
			LastName:  "Lovelace",
			ProfileID: "profile-1",
			IsActive:  true,
		},
	}, sessionRepo, nil)
	oauthRepo := &oauthRepoStub{
		code: &ports.OAuthCodeData{
			ClientID:          "client-1",
			RedirectURI:       "https://client.example/callback",
			Scope:             scopes,
			UserID:            "user-1",
			ClientTypeAtIssue: OAuthClientTypeConfidential,
		},
	}
	svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, authSvc, oauthRepo)
	svc.SetTokenResponseProfiles([]TokenResponseProfileRegistration{
		testTokenResponseProfileWithID("alpha-compatible"),
		testTokenResponseProfileWithID("beta-compatible"),
	})

	token, err := svc.ExchangeTokenForRequest(context.Background(), TokenExchangeRequest{
		ClientID:     "client-1",
		ClientSecret: secret,
		Code:         "code-1",
		RedirectURI:  "https://client.example/callback",
	})
	if err == nil {
		t.Fatalf("ExchangeTokenForRequest token=%#v, want overlapping token response profile error", token)
	}
	oauthErr, ok := OAuthProtocolErrorFrom(err)
	if !ok || oauthErr.Code != "server_error" || oauthErr.Reason != "token_response_profile_conflict" {
		t.Fatalf("error = %#v, want token_response_profile_conflict server_error", err)
	}
	if sessionRepo.inserted != nil {
		t.Fatalf("ExchangeTokenForRequest inserted session on token response profile conflict: %#v", sessionRepo.inserted)
	}
	if oauthRepo.codeConsumed {
		t.Fatal("ExchangeTokenForRequest consumed auth code on token response profile conflict")
	}
}

func testTokenResponseProfile() TokenResponseProfileRegistration {
	return testTokenResponseProfileWithID("test-compatible")
}

func testTokenResponseProfileWithID(profileID string) TokenResponseProfileRegistration {
	return TokenResponseProfileRegistration{
		ProfileID: profileID,
		Applies: func(ctx TokenResponseProfileContext) bool {
			return strings.TrimSpace(ctx.ConnectedAppID) != "" &&
				strings.EqualFold(strings.TrimSpace(ctx.SessionClass), "salesforce_compat")
		},
		Build: func(ctx TokenResponseProfileContext) map[string]any {
			return map[string]any{
				"id":        strings.TrimRight(ctx.InstanceURL, "/") + "/id/00D000000000001/" + ctx.UserID,
				"signature": ctx.ClientSecret + ":" + ctx.IssuedAt,
			}
		},
	}
}

func TestExchangeTokenForRequestConfidentialRejectsScopedCodeWhenAppScopesCleared(t *testing.T) {
	confidentialType := OAuthClientTypeConfidential
	tokenAuth := OAuthTokenAuthClientSecretPost
	grants := OAuthGrantAuthorizationCode
	redirects := `["https://client.example/callback"]`
	emptyScopes := ""
	secret := "client-secret"
	secretHash, err := pkgauth.HashPassword(secret)
	if err != nil {
		t.Fatalf("HashPassword: %v", err)
	}

	tests := []struct {
		name   string
		scopes *string
	}{
		{name: "nil scopes"},
		{name: "empty scopes", scopes: &emptyScopes},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			app := &models.SystemConnectedApp{
				ID:                      "app-1",
				ConsumerKey:             "client-1",
				ConsumerSecretHash:      &secretHash,
				RedirectURIs:            &redirects,
				Scopes:                  tt.scopes,
				IsActive:                true,
				ClientType:              &confidentialType,
				TokenEndpointAuthMethod: &tokenAuth,
				AllowedGrantTypes:       &grants,
			}
			authSvc := NewAuthService(&userRepoStub{
				user: &models.User{
					ID:        "user-1",
					Username:  "ada@example.com",
					Email:     "ada@example.com",
					FirstName: "Ada",
					LastName:  "Lovelace",
					ProfileID: "profile-1",
					IsActive:  true,
				},
			}, &sessionRepoStub{}, nil)
			svc := NewOAuthService(nil, &systemRepoStub{app: app, permitted: true}, nil, authSvc, &oauthRepoStub{
				code: &ports.OAuthCodeData{
					ClientID:          "client-1",
					RedirectURI:       "https://client.example/callback",
					Scope:             "api",
					UserID:            "user-1",
					ClientTypeAtIssue: OAuthClientTypeConfidential,
				},
			})

			_, err := svc.ExchangeTokenForRequest(context.Background(), TokenExchangeRequest{
				ClientID:     "client-1",
				ClientSecret: secret,
				Code:         "code-1",
				RedirectURI:  "https://client.example/callback",
			})
			if err == nil {
				t.Fatal("ExchangeTokenForRequest succeeded after scoped code policy was cleared, want invalid_grant")
			}
			oauthErr, ok := OAuthProtocolErrorFrom(err)
			if !ok || oauthErr.Code != "invalid_grant" || oauthErr.Reason != "scope_policy_changed" {
				t.Fatalf("error = %#v, want invalid_grant scope_policy_changed", err)
			}
		})
	}
}

func pkcePair() (string, string) {
	verifier := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"
	sum := sha256.Sum256([]byte(verifier))
	return verifier, base64.RawURLEncoding.EncodeToString(sum[:])
}

type oauthRepoStub struct {
	stored            *ports.OAuthCodeCreateRequest
	code              *ports.OAuthCodeData
	codeConsumed      bool
	authorizeStored   *ports.OAuthAuthorizeRequestCreateRequest
	authorizeData     *ports.OAuthAuthorizeRequestData
	authorizeConsumed bool
	// authorizeExpectedHandle makes namespace-sensitive tests fail if the
	// service addresses a different shared-store row.
	authorizeExpectedHandle string
	// authorizeFetchedHandle records the exact shared-store namespace addressed
	// by a login-state lookup.
	authorizeFetchedHandle  string
	authorizeConsumedHandle string
	consumeAndStoreHit      bool
	consumeAndStoreErr      error

	refreshByHash                       map[string]*models.SystemOAuthRefreshToken
	refreshStored                       []*models.SystemOAuthRefreshToken
	refreshRevoked                      []string
	refreshFamilyRevoked                []string
	refreshRevokeForceFalse             bool
	grantID                             string
	grantInactive                       bool
	connections                         []ports.AuthorizationGrantConnection
	disconnectedSubjectID               string
	disconnectedClientID                string
	disconnectedRevokedBy               string
	consumedUnboundCodesSubjectID       string
	consumedUnboundCodesLockingClientID string
}

func (r *oauthRepoStub) EnsureAuthorizationGrant(_ context.Context, request ports.AuthorizationGrantRequest) (ports.AuthorizationGrant, error) {
	if r.grantID == "" {
		r.grantID = "grant-1"
	}
	return ports.AuthorizationGrant{
		ID:            r.grantID,
		SubjectID:     request.SubjectID,
		ClientID:      request.ClientID,
		Scope:         request.Scope,
		PolicyVersion: request.PolicyVersion,
	}, nil
}

func (r *oauthRepoStub) AuthorizationGrantActive(context.Context, string, string, string, int) (bool, error) {
	return !r.grantInactive, nil
}

func (r *oauthRepoStub) RevokeAuthorizationGrantsForSubjectAndClient(_ context.Context, subjectID, clientID, revokedBy string) (int, error) {
	r.disconnectedSubjectID = subjectID
	r.disconnectedClientID = clientID
	r.disconnectedRevokedBy = revokedBy
	r.grantInactive = true
	return 1, nil
}

func (r *oauthRepoStub) ConsumeUnboundOAuthCodesForSubject(_ context.Context, subjectID, lockingClientID string) (int, error) {
	r.consumedUnboundCodesSubjectID = subjectID
	r.consumedUnboundCodesLockingClientID = lockingClientID
	return 1, nil
}

func (r *oauthRepoStub) RevokeAuthorizationGrantsForClient(context.Context, string, string) (int, error) {
	r.grantInactive = true
	return 1, nil
}

func (r *oauthRepoStub) ListLiveAuthorizationGrantConnections(context.Context, string) ([]ports.AuthorizationGrantConnection, error) {
	return append([]ports.AuthorizationGrantConnection(nil), r.connections...), nil
}

func (r *oauthRepoStub) StoreAuthCode(context.Context, string, string, string, string, string, string, time.Time) error {
	return nil
}

func (r *oauthRepoStub) StoreAuthCodeRequest(_ context.Context, req ports.OAuthCodeCreateRequest) error {
	copied := req
	r.stored = &copied
	return nil
}

func (r *oauthRepoStub) StoreAuthorizeRequest(_ context.Context, req ports.OAuthAuthorizeRequestCreateRequest) error {
	copied := req
	r.authorizeStored = &copied
	r.authorizeData = &ports.OAuthAuthorizeRequestData{
		ClientID:            req.ClientID,
		RedirectURI:         req.RedirectURI,
		Scope:               req.Scope,
		State:               req.State,
		CodeChallenge:       req.CodeChallenge,
		CodeChallengeMethod: req.CodeChallengeMethod,
		Issuer:              req.Issuer,
		Resource:            req.Resource,
		ExpiresAt:           req.ExpiresAt,
	}
	return nil
}

func (r *oauthRepoStub) GetAuthCode(context.Context, string) (*ports.OAuthCodeData, error) {
	return r.code, nil
}

func (r *oauthRepoStub) ConsumeAuthCode(context.Context, string) (*ports.OAuthCodeData, error) {
	r.codeConsumed = true
	return r.code, nil
}

func (r *oauthRepoStub) GetAuthorizeRequest(_ context.Context, handle string) (*ports.OAuthAuthorizeRequestData, error) {
	r.authorizeFetchedHandle = handle
	if r.authorizeExpectedHandle != "" && handle != r.authorizeExpectedHandle {
		return nil, nil
	}
	if r.authorizeConsumed {
		return nil, nil
	}
	return r.authorizeData, nil
}

func (r *oauthRepoStub) ConsumeAuthorizeRequest(_ context.Context, handle string) (*ports.OAuthAuthorizeRequestData, error) {
	r.authorizeConsumedHandle = handle
	if r.authorizeExpectedHandle != "" && handle != r.authorizeExpectedHandle {
		return nil, nil
	}
	if r.authorizeConsumed {
		return nil, nil
	}
	r.authorizeConsumed = true
	return r.authorizeData, nil
}

func (r *oauthRepoStub) ConsumeAuthorizeRequestAndStoreAuthCode(_ context.Context, _ string, req ports.OAuthCodeCreateRequest) (*ports.OAuthAuthorizeRequestData, error) {
	if r.authorizeConsumed {
		return nil, nil
	}
	r.consumeAndStoreHit = true
	if r.consumeAndStoreErr != nil {
		return nil, r.consumeAndStoreErr
	}
	r.authorizeConsumed = true
	copied := req
	r.stored = &copied
	return r.authorizeData, nil
}

func (r *oauthRepoStub) CleanupExpiredCodes(context.Context) (int64, error) {
	return 0, nil
}

func (r *oauthRepoStub) StoreRefreshToken(_ context.Context, t *models.SystemOAuthRefreshToken) error {
	if r.refreshByHash == nil {
		r.refreshByHash = map[string]*models.SystemOAuthRefreshToken{}
	}
	cp := *t
	r.refreshByHash[t.TokenHash] = &cp
	r.refreshStored = append(r.refreshStored, &cp)
	return nil
}

func (r *oauthRepoStub) GetRefreshTokenByHash(_ context.Context, hash string) (*models.SystemOAuthRefreshToken, error) {
	if t, ok := r.refreshByHash[hash]; ok {
		cp := *t
		return &cp, nil
	}
	return nil, nil
}

func (r *oauthRepoStub) RevokeRefreshToken(_ context.Context, id string) (bool, error) {
	if r.refreshRevokeForceFalse {
		// Simulate losing the atomic single-use CAS (a concurrent caller already
		// flipped IsRevoked 0→1): the row exists but no rows are affected.
		return false, nil
	}
	for _, t := range r.refreshByHash {
		if t.ID == id && !t.IsRevoked {
			t.IsRevoked = true
			r.refreshRevoked = append(r.refreshRevoked, id)
			return true, nil
		}
	}
	return false, nil
}

func (r *oauthRepoStub) RevokeRefreshTokenFamily(_ context.Context, familyID string) (int, error) {
	if strings.TrimSpace(familyID) == "" {
		return 0, nil
	}
	r.refreshFamilyRevoked = append(r.refreshFamilyRevoked, familyID)
	n := 0
	for _, t := range r.refreshByHash {
		if t.FamilyID != nil && *t.FamilyID == familyID && !t.IsRevoked {
			t.IsRevoked = true
			n++
		}
	}
	return n, nil
}

func (r *oauthRepoStub) RevokeRefreshTokensForUser(_ context.Context, userID string) (int, error) {
	n := 0
	for _, t := range r.refreshByHash {
		if t.UserID == userID && !t.IsRevoked {
			t.IsRevoked = true
			n++
		}
	}
	return n, nil
}

func (r *oauthRepoStub) RevokeRefreshTokensForConnectedApp(_ context.Context, appID string) (int, error) {
	n := 0
	for _, t := range r.refreshByHash {
		if t.ConnectedAppID != nil && *t.ConnectedAppID == appID && !t.IsRevoked {
			t.IsRevoked = true
			n++
		}
	}
	return n, nil
}

func (r *oauthRepoStub) RevokeRefreshTokensForUserAndConnectedApp(_ context.Context, userID, appID string) (int, error) {
	n := 0
	for _, t := range r.refreshByHash {
		if t.UserID == userID && t.ConnectedAppID != nil && *t.ConnectedAppID == appID && !t.IsRevoked {
			t.IsRevoked = true
			n++
		}
	}
	return n, nil
}

// RotateRefreshToken mirrors the atomic repo contract in memory: consume the
// presented token and store the successor, or (on a revoked/lost-CAS token)
// revoke the whole family and report reuse without storing a successor.
func (r *oauthRepoStub) RotateRefreshToken(_ context.Context, presentedHash string, successor *models.SystemOAuthRefreshToken) (bool, bool, error) {
	presented := r.refreshByHash[presentedHash]
	if presented == nil {
		return false, false, nil
	}
	familyID := ""
	if presented.FamilyID != nil {
		familyID = *presented.FamilyID
	}
	sweep := func() {
		if familyID != "" {
			r.refreshFamilyRevoked = append(r.refreshFamilyRevoked, familyID)
			for _, t := range r.refreshByHash {
				if t.FamilyID != nil && *t.FamilyID == familyID && !t.IsRevoked {
					t.IsRevoked = true
				}
			}
			return
		}
		presented.IsRevoked = true
	}
	if presented.IsRevoked || r.refreshRevokeForceFalse {
		sweep()
		return false, true, nil
	}
	presented.IsRevoked = true
	r.refreshRevoked = append(r.refreshRevoked, presented.ID)
	if r.refreshByHash == nil {
		r.refreshByHash = map[string]*models.SystemOAuthRefreshToken{}
	}
	cp := *successor
	r.refreshByHash[successor.TokenHash] = &cp
	r.refreshStored = append(r.refreshStored, &cp)
	return true, false, nil
}

type userRepoStub struct {
	user         *models.User
	passwordHash string
}

func (r *userRepoStub) CheckUserExistsByEmail(context.Context, string) (bool, error) {
	return false, nil
}
func (r *userRepoStub) CheckUserExistsByID(context.Context, string) (bool, error) { return false, nil }
func (r *userRepoStub) CheckEmailConflict(context.Context, string, string) (bool, error) {
	return false, nil
}
func (r *userRepoStub) UpdateUser(context.Context, string, map[string]interface{}) error { return nil }
func (r *userRepoStub) DeleteUser(context.Context, string) error                         { return nil }
func (r *userRepoStub) FindAll(context.Context) ([]*models.User, error)                  { return nil, nil }
func (r *userRepoStub) GetUserRoleID(context.Context, string) (*string, error)           { return nil, nil }
func (r *userRepoStub) FindUserByEmailWithPassword(context.Context, string) (*ports.UserWithPassword, error) {
	if r.user == nil || r.passwordHash == "" {
		return nil, nil
	}
	return &ports.UserWithPassword{User: r.user, PasswordHash: r.passwordHash}, nil
}
func (r *userRepoStub) FindUserByIDWithPassword(context.Context, string) (*ports.UserWithPassword, error) {
	if r.user == nil || r.passwordHash == "" {
		return nil, nil
	}
	return &ports.UserWithPassword{User: r.user, PasswordHash: r.passwordHash}, nil
}
func (r *userRepoStub) UpdatePassword(context.Context, string, string) error { return nil }
func (r *userRepoStub) GetUserByID(context.Context, string) (*models.User, error) {
	return r.user, nil
}
func (r *userRepoStub) CreateUser(context.Context, *models.User) error { return nil }
func (r *userRepoStub) GetFirstUserIDByProfile(context.Context, string) (string, error) {
	return "", nil
}
func (r *userRepoStub) GetRoleNameByID(context.Context, string) (string, error) { return "", nil }
func (r *userRepoStub) GetProfileNameByID(context.Context, string) (string, error) {
	return "Standard User", nil
}
func (r *userRepoStub) GetUserIDsByRoleIDs(context.Context, []string) ([]string, error) {
	return nil, nil
}
func (r *userRepoStub) ResetFailedLoginAttempts(context.Context, string) error { return nil }
func (r *userRepoStub) IncrementFailedAndLockIfNeeded(context.Context, string, int, time.Duration) (bool, error) {
	return false, nil
}

type sessionRepoStub struct {
	inserted                  *models.SystemSession
	insertErr                 error
	loaded                    *models.SystemSession
	connectedAppAlive         *bool
	renewedSessionID          string
	renewedPreviousToken      string
	renewedToken              string
	renewedExpiresAt          time.Time
	renewedNow                time.Time
	renewedResult             *bool
	competingToken            string
	competingExpiresAt        time.Time
	revokedSessionID          string
	revokeExpectedToken       string
	controlRevokedSessionID   string
	controlRevokeProofHash    string
	controlRenewedSessionID   string
	controlRenewPreviousToken string
	controlRenewProofHash     string
	controlRenewedToken       string
	controlRenewedResult      *bool
	disconnectedUserID        string
	disconnectedClientID      string
}

func (r *sessionRepoStub) InsertSession(_ context.Context, session *models.SystemSession) error {
	if r.insertErr != nil {
		return r.insertErr
	}
	r.inserted = session
	return nil
}
func (r *sessionRepoStub) GetSession(context.Context, string) (*models.SystemSession, error) {
	return r.loaded, nil
}
func (r *sessionRepoStub) AdvanceSessionAuthEpoch(context.Context, string, uint64) (uint64, error) {
	return 0, nil
}
func (r *sessionRepoStub) RevokeSession(_ context.Context, sessionID, expectedToken string) (bool, error) {
	r.revokeExpectedToken = expectedToken
	if r.loaded == nil || r.loaded.ID != sessionID || r.loaded.Token != expectedToken || r.loaded.IsRevoked {
		return false, nil
	}
	r.loaded.IsRevoked = true
	r.revokedSessionID = sessionID
	return true, nil
}
func (r *sessionRepoStub) RevokeSessionWithControlProof(_ context.Context, sessionID, expectedControlProofHash string) (bool, error) {
	r.controlRevokeProofHash = expectedControlProofHash
	if r.loaded == nil || r.loaded.ID != sessionID || r.loaded.ControlProofHash == nil ||
		*r.loaded.ControlProofHash != expectedControlProofHash || r.loaded.IsRevoked {
		return false, nil
	}
	r.loaded.IsRevoked = true
	r.loaded.ControlProofHash = nil
	r.controlRevokedSessionID = sessionID
	return true, nil
}
func (r *sessionRepoStub) RevokeAllSessionsForUser(context.Context, string) error { return nil }
func (r *sessionRepoStub) RevokeAllSessionsByProfileID(context.Context, string) (int64, error) {
	return 0, nil
}
func (r *sessionRepoStub) RevokeAllSessionsByUserRoleID(context.Context, string) (int64, error) {
	return 0, nil
}
func (r *sessionRepoStub) RevokeAllSessionsForConnectedApp(context.Context, string) (int64, error) {
	return 0, nil
}
func (r *sessionRepoStub) RevokeSessionsForUserAndConnectedApp(_ context.Context, userID, clientID string) (int64, error) {
	r.disconnectedUserID = userID
	r.disconnectedClientID = clientID
	return 1, nil
}
func (r *sessionRepoStub) RenewSession(_ context.Context, sessionID, previousToken, token string, expiresAt, now time.Time) (bool, error) {
	r.renewedSessionID = sessionID
	r.renewedPreviousToken = previousToken
	r.renewedToken = token
	r.renewedExpiresAt = expiresAt
	r.renewedNow = now
	if r.renewedResult != nil && !*r.renewedResult {
		if r.loaded != nil && r.competingToken != "" {
			competing := *r.loaded
			competing.Token = r.competingToken
			competing.ExpiresAt = r.competingExpiresAt
			competing.LastActivity = now
			r.loaded = &competing
		}
		return false, nil
	}
	if r.loaded != nil {
		r.loaded.Token = token
		r.loaded.ExpiresAt = expiresAt
		r.loaded.LastActivity = now
	}
	if r.renewedResult != nil {
		return *r.renewedResult, nil
	}
	return true, nil
}
func (r *sessionRepoStub) RenewSessionWithControlProof(_ context.Context, sessionID, previousToken, expectedControlProofHash, token string, expiresAt, now time.Time) (bool, error) {
	r.controlRenewedSessionID = sessionID
	r.controlRenewPreviousToken = previousToken
	r.controlRenewProofHash = expectedControlProofHash
	r.controlRenewedToken = token
	if r.loaded == nil || r.loaded.ID != sessionID || r.loaded.Token != previousToken || r.loaded.ControlProofHash == nil ||
		*r.loaded.ControlProofHash != expectedControlProofHash || r.loaded.IsRevoked || !r.loaded.ExpiresAt.After(now) {
		return false, nil
	}
	if r.controlRenewedResult != nil && !*r.controlRenewedResult {
		if r.competingToken != "" {
			competing := *r.loaded
			competing.Token = r.competingToken
			competing.ExpiresAt = r.competingExpiresAt
			competing.LastActivity = now
			r.loaded = &competing
		}
		return false, nil
	}
	r.loaded.Token = token
	r.loaded.ExpiresAt = expiresAt
	r.loaded.LastActivity = now
	if r.controlRenewedResult != nil {
		return *r.controlRenewedResult, nil
	}
	return true, nil
}
func (r *sessionRepoStub) UpdateLastActivity(context.Context, string) error { return nil }
func (r *sessionRepoStub) CleanupExpiredSessions(context.Context, time.Duration) (int64, error) {
	return 0, nil
}
func (r *sessionRepoStub) IsConnectedAppActive(context.Context, string) (bool, error) {
	if r.connectedAppAlive != nil {
		return *r.connectedAppAlive, nil
	}
	return true, nil
}

type systemRepoStub struct {
	app                      *models.SystemConnectedApp
	permitted                bool
	err                      error
	beforeCredentialIssuance func()
}

type oauthCredentialRevocationRaceState struct {
	issuance           sync.Mutex
	mu                 sync.Mutex
	issuanceActive     bool
	permitted          bool
	activeSession      bool
	activeRefreshToken bool
	permissionRead     chan struct{}
	revocationDone     chan struct{}
}

func newOAuthCredentialRevocationRaceState() *oauthCredentialRevocationRaceState {
	return &oauthCredentialRevocationRaceState{
		permitted:      true,
		permissionRead: make(chan struct{}),
		revocationDone: make(chan struct{}),
	}
}

func (s *oauthCredentialRevocationRaceState) revokeConnectedAppCredentials() {
	<-s.permissionRead
	s.issuance.Lock()
	s.mu.Lock()
	s.permitted = false
	s.activeSession = false
	s.activeRefreshToken = false
	s.mu.Unlock()
	s.issuance.Unlock()
	close(s.revocationDone)
}

type oauthCredentialRaceSystemRepo struct {
	*systemRepoStub
	state *oauthCredentialRevocationRaceState
}

func (r *oauthCredentialRaceSystemRepo) IsUserPermittedForApp(context.Context, string, string) (bool, error) {
	select {
	case <-r.state.permissionRead:
	default:
		close(r.state.permissionRead)
	}
	r.state.mu.Lock()
	defer r.state.mu.Unlock()
	return r.state.permitted, nil
}

func (r *oauthCredentialRaceSystemRepo) WithConnectedAppCredentialIssuance(
	ctx context.Context,
	_ string,
	issue func(context.Context) error,
) error {
	r.state.issuance.Lock()
	r.state.mu.Lock()
	r.state.issuanceActive = true
	r.state.mu.Unlock()
	defer func() {
		r.state.mu.Lock()
		r.state.issuanceActive = false
		r.state.mu.Unlock()
		r.state.issuance.Unlock()
	}()
	return issue(ctx)
}

type oauthCredentialRaceSessionRepo struct {
	*sessionRepoStub
	state *oauthCredentialRevocationRaceState
}

func (r *oauthCredentialRaceSessionRepo) InsertSession(context.Context, *models.SystemSession) error {
	r.state.mu.Lock()
	r.state.activeSession = true
	r.state.mu.Unlock()
	return nil
}

type oauthCredentialRaceOAuthRepo struct {
	*oauthRepoStub
	state *oauthCredentialRevocationRaceState
}

func (r *oauthCredentialRaceOAuthRepo) ConsumeAuthCode(
	context.Context,
	string,
) (*ports.OAuthCodeData, error) {
	r.state.mu.Lock()
	issuanceActive := r.state.issuanceActive
	r.state.mu.Unlock()
	if !issuanceActive {
		<-r.state.revocationDone
	}
	r.codeConsumed = true
	return r.code, nil
}

func (r *oauthCredentialRaceOAuthRepo) StoreRefreshToken(
	context.Context,
	*models.SystemOAuthRefreshToken,
) error {
	r.state.mu.Lock()
	r.state.activeRefreshToken = true
	r.state.mu.Unlock()
	return nil
}

func (r *systemRepoStub) GetConnectedAppByClientID(context.Context, string) (*models.SystemConnectedApp, error) {
	if r.err != nil {
		return nil, r.err
	}
	return r.app, nil
}

func (r *systemRepoStub) IsUserPermittedForApp(context.Context, string, string) (bool, error) {
	return r.permitted, nil
}

func (r *systemRepoStub) WithConnectedAppCredentialIssuance(
	ctx context.Context,
	_ string,
	issue func(context.Context) error,
) error {
	if r.beforeCredentialIssuance != nil {
		r.beforeCredentialIssuance()
	}
	return issue(ports.WithAuthorizationGrantTransactionScope(ctx))
}

func (r *systemRepoStub) GetLogs(context.Context, int) ([]*models.SystemLog, error) { return nil, nil }
func (r *systemRepoStub) GetRecentItems(context.Context, string, int) ([]*models.SystemRecent, error) {
	return nil, nil
}
func (r *systemRepoStub) GetConfig(context.Context, string) (*string, error) { return nil, nil }
func (r *systemRepoStub) GetAllConfigs(context.Context) ([]*models.SystemConfig, error) {
	return nil, nil
}
func (r *systemRepoStub) GetConfigID(context.Context, string) (string, error) { return "", nil }
func (r *systemRepoStub) UpsertConfig(context.Context, *models.SystemConfig) error {
	return nil
}
func (r *systemRepoStub) DeleteConfig(context.Context, string) error { return nil }
func (r *systemRepoStub) GetProfileIDByName(context.Context, string) (string, error) {
	return "", nil
}
func (r *systemRepoStub) GetUserIDByEmail(context.Context, string) (string, error) {
	return "", nil
}
func (r *systemRepoStub) BatchUpsertProfiles(context.Context, []models.Profile) error {
	return nil
}
func (r *systemRepoStub) BatchUpsertUsers(context.Context, []models.User) error {
	return nil
}
func (r *systemRepoStub) ForceUpdateUserPassword(context.Context, string, string) error {
	return nil
}
