package innerauth

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

	"github.com/mem9-ai/drive9/pkg/metrics"
	"github.com/mem9-ai/drive9/pkg/tenant"
)

func TestJWTFromTiDBCloudAuthorizationHeader(t *testing.T) {
	want := makeJWT(`{"orgId":60205}`)
	header := make(http.Header)
	header.Set("X-TiDB-Cloud-Authorization", "Bearer "+want)
	got, err := JWTFromHeader(header)
	if err != nil {
		t.Fatalf("JWTFromHeader: %v", err)
	}
	if got != want {
		t.Fatalf("JWT = %q, want header token", got)
	}
}

func TestSelectHighestProjectScope(t *testing.T) {
	scopes := []APIKeyResourceInfo{
		{ScopeType: "ORG", ResourceID: 60205, Role: "org:member"},
		{ScopeType: "PROJECT", ResourceID: 103547, Role: "project:readonly"},
		{ScopeType: "PROJECT", ResourceID: 103548, Role: "project:owner"},
		{ScopeType: "PROJECT", ResourceID: 103549, Role: "project:dev"},
	}

	got, ok := SelectHighestProjectScope(scopes)
	if !ok {
		t.Fatal("selectHighestProjectScope() found no project scope")
	}
	if got.ResourceID != 103548 || got.Role != "project:owner" {
		t.Fatalf("selected project scope = %#v, want project owner on 103548", got)
	}
}

func TestSelectHighestProjectScopePreservesFirstEqualPriority(t *testing.T) {
	scopes := []APIKeyResourceInfo{
		{ScopeType: "PROJECT", ResourceID: 103547, Role: "project:dev"},
		{ScopeType: "PROJECT", ResourceID: 103548, Role: "project:dev"},
	}

	got, ok := SelectHighestProjectScope(scopes)
	if !ok || got.ResourceID != 103547 {
		t.Fatalf("selected project scope = %#v, want first equal-priority project", got)
	}
}

func TestSelectHighestProjectScopeSkipsZeroResourceID(t *testing.T) {
	scopes := []APIKeyResourceInfo{
		{ScopeType: "PROJECT", ResourceID: 0, Role: "project:owner"},
		{ScopeType: "PROJECT", ResourceID: 103547, Role: "project:dev"},
	}

	got, ok := SelectHighestProjectScope(scopes)
	if !ok || got.ResourceID != 103547 || got.Role != "project:dev" {
		t.Fatalf("selected project scope = %#v, want valid project dev scope", got)
	}

	if _, ok := SelectHighestProjectScope([]APIKeyResourceInfo{{ScopeType: "PROJECT", Role: "project:owner"}}); ok {
		t.Fatal("zero-id project scope was selected")
	}
}

func TestBearerJWTAndExternalOrganizationID(t *testing.T) {
	tests := []struct {
		name    string
		header  string
		orgID   string
		want    uint64
		wantErr error
	}{
		{name: "numeric orgId query", header: "Bearer " + makeJWT(`{"orgId":99999}`), orgID: "60205", want: 60205},
		{name: "scheme is case insensitive", header: "bearer " + makeJWT(`{"sub":"user"}`), orgID: "7", want: 7},
		{name: "missing bearer", header: makeJWT(`{"sub":"user"}`), orgID: "7", wantErr: ErrMalformedAuthorization},
		{name: "malformed jwt", header: "Bearer not-a-jwt", orgID: "7", wantErr: ErrMalformedAuthorization},
		{name: "missing orgId query", header: "Bearer " + makeJWT(`{"orgId":60205}`), wantErr: ErrInvalidJWTOrganizationID},
		{name: "zero orgId query", header: "Bearer " + makeJWT(`{"sub":"user"}`), orgID: "0", wantErr: ErrInvalidJWTOrganizationID},
		{name: "negative orgId query", header: "Bearer " + makeJWT(`{"sub":"user"}`), orgID: "-1", wantErr: ErrInvalidJWTOrganizationID},
		{name: "fractional orgId query", header: "Bearer " + makeJWT(`{"sub":"user"}`), orgID: "1.5", wantErr: ErrInvalidJWTOrganizationID},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := BearerJWT(tt.header)
			if tt.wantErr != nil && errors.Is(err, tt.wantErr) {
				return
			}
			if err != nil {
				t.Fatalf("BearerJWT: %v", err)
			}
			got, err := ParseOrganizationID(tt.orgID)
			if !errors.Is(err, tt.wantErr) {
				t.Fatalf("ParseOrganizationID error = %v, want errors.Is(..., %v)", err, tt.wantErr)
			}
			if err == nil && got != tt.want {
				t.Fatalf("organization ID = %d, want %d", got, tt.want)
			}
		})
	}
}

func TestDecodeJWTPartReportsPaddedDecodeFailure(t *testing.T) {
	_, err := decodeJWTPart("AA=")
	if err == nil {
		t.Fatal("decodeJWTPart succeeded for invalid padding")
	}
	if got, want := err.Error(), "decode JWT part: illegal base64 data at input byte 3"; got != want {
		t.Fatalf("error = %q, want %q", got, want)
	}
}

func TestEncodeAPIKeyUsesPaddedURLSafeBase64(t *testing.T) {
	got, err := EncodeAPIKey(tenant.CredentialProvisionRequest{PublicKey: "\xfb", PrivateKey: "\xffx"})
	if err != nil {
		t.Fatalf("EncodeAPIKey: %v", err)
	}
	if got != "-zr_eA==" {
		t.Fatalf("encoded API key = %q, want padded URL-safe Base64 %q", got, "-zr_eA==")
	}
}

func TestClassifyCredentialsRejectsAmbiguousAndPartialCredentials(t *testing.T) {
	jwtHeader := "Bearer " + makeJWT(`{"orgId":42}`)
	tests := []struct {
		name    string
		header  string
		cred    tenant.CredentialProvisionRequest
		want    CredentialKind
		wantErr error
	}{
		{name: "jwt", header: jwtHeader, want: CredentialJWT},
		{name: "api key", cred: tenant.CredentialProvisionRequest{PublicKey: "public", PrivateKey: "private"}, want: CredentialAPIKey},
		{name: "ambiguous", header: jwtHeader, cred: tenant.CredentialProvisionRequest{PublicKey: "public", PrivateKey: "private"}, wantErr: ErrAmbiguousCredentials},
		{name: "partial public", cred: tenant.CredentialProvisionRequest{PublicKey: "public"}, wantErr: tenant.ErrPartialCredentials},
		{name: "partial private", cred: tenant.CredentialProvisionRequest{PrivateKey: "private"}, wantErr: tenant.ErrPartialCredentials},
		{name: "missing", wantErr: tenant.ErrCredentialsRequired},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ClassifyCredentials(tt.header, tt.cred)
			if !errors.Is(err, tt.wantErr) {
				t.Fatalf("error = %v, want errors.Is(..., %v)", err, tt.wantErr)
			}
			if err == nil && got != tt.want {
				t.Fatalf("kind = %q, want %q", got, tt.want)
			}
		})
	}
}

func TestDispatcherBuildsJWTSubjectWithExternalOrganizationAndDefaultExecutionCredentials(t *testing.T) {
	account := &fakeAccountProvider{userAuthnResult: UserAuthnResult{UserID: 123}}
	defaults := &fakeDefaultCredentials{credentials: tenant.CredentialProvisionRequest{PublicKey: "server-public", PrivateKey: "server-private"}, ok: true}
	dispatcher := NewDispatcher(account, defaults, time.Hour)

	subject, err := dispatcher.Authenticate(context.Background(), "Bearer "+makeJWT(`{"orgId":99999}`), 60205, tenant.CredentialProvisionRequest{})
	if err != nil {
		t.Fatalf("Authenticate: %v", err)
	}
	if subject.OrganizationID != 60205 || subject.UserID != 123 || subject.PublicKey != "" || subject.Kind != CredentialJWT {
		t.Fatalf("subject = %#v", subject)
	}
	if subject.ExecutionCredentials == nil || *subject.ExecutionCredentials != defaults.credentials {
		t.Fatalf("execution credentials = %#v, want server defaults", subject.ExecutionCredentials)
	}
	if account.gotJWT == "" {
		t.Fatal("account provider did not receive JWT")
	}
}

func TestDispatcherBuildsAPIKeySubjectWithDefaultExecutionCredentials(t *testing.T) {
	account := &fakeAccountProvider{apiKeyAuthnResult: APIKeyAuthnResult{PublicKey: "caller-public", OrganizationID: 60205}}
	defaults := &fakeDefaultCredentials{credentials: tenant.CredentialProvisionRequest{PublicKey: "server-public", PrivateKey: "server-private"}, ok: true}
	dispatcher := NewDispatcher(account, defaults, time.Hour)
	caller := tenant.CredentialProvisionRequest{PublicKey: "caller-public", PrivateKey: "caller-private"}

	subject, err := dispatcher.Authenticate(context.Background(), "", 0, caller)
	if err != nil {
		t.Fatalf("Authenticate: %v", err)
	}
	if subject.OrganizationID != 60205 || subject.UserID != 0 || subject.PublicKey != "caller-public" || subject.Kind != CredentialAPIKey {
		t.Fatalf("subject = %#v", subject)
	}
	if subject.ExecutionCredentials == nil || *subject.ExecutionCredentials != defaults.credentials {
		t.Fatalf("execution credentials = %#v, want server defaults", subject.ExecutionCredentials)
	}
	if account.gotAPIKey != "Y2FsbGVyLXB1YmxpYzpjYWxsZXItcHJpdmF0ZQ==" {
		t.Fatalf("encoded API key = %q, want padded URL-safe encoding of AK:SK", account.gotAPIKey)
	}
}

func TestDispatcherRejectsAPIKeyAuthenticationSubjectMismatch(t *testing.T) {
	account := &fakeAccountProvider{apiKeyAuthnResult: APIKeyAuthnResult{PublicKey: "different-public", OrganizationID: 60205}}
	dispatcher := NewDispatcher(account, nil, time.Hour)

	_, err := dispatcher.Authenticate(context.Background(), "", 0, tenant.CredentialProvisionRequest{
		PublicKey: "caller-public", PrivateKey: "caller-private",
	})
	if !errors.Is(err, ErrInvalidResponse) {
		t.Fatalf("error = %v, want errors.Is(..., %v)", err, ErrInvalidResponse)
	}
}

func TestDispatcherRejectsAPIKeyWithoutDefaultExecutionCredentials(t *testing.T) {
	account := &fakeAccountProvider{apiKeyAuthnResult: APIKeyAuthnResult{PublicKey: "caller-public", OrganizationID: 60205}}
	dispatcher := NewDispatcher(account, nil, time.Hour)

	_, err := dispatcher.Authenticate(context.Background(), "", 0, tenant.CredentialProvisionRequest{
		PublicKey: "caller-public", PrivateKey: "caller-private",
	})
	if !errors.Is(err, ErrDefaultCredentialsUnavailable) {
		t.Fatalf("error = %v, want errors.Is(..., %v)", err, ErrDefaultCredentialsUnavailable)
	}
}

func TestDispatcherCachesSuccessfulAPIKeyAuthentication(t *testing.T) {
	account := &fakeAccountProvider{apiKeyAuthnResult: APIKeyAuthnResult{PublicKey: "caller-public", OrganizationID: 60205}}
	dispatcher := NewDispatcher(account, &fakeDefaultCredentials{credentials: tenant.CredentialProvisionRequest{PublicKey: "shared-public", PrivateKey: "shared-private"}, ok: true}, time.Hour)
	caller := tenant.CredentialProvisionRequest{PublicKey: "caller-public", PrivateKey: "caller-private"}

	for i := 0; i < 2; i++ {
		subject, err := dispatcher.Authenticate(context.Background(), "", 0, caller)
		if err != nil {
			t.Fatalf("Authenticate call %d: %v", i+1, err)
		}
		if subject.OrganizationID != 60205 || subject.PublicKey != "caller-public" || subject.Kind != CredentialAPIKey {
			t.Fatalf("subject = %#v", subject)
		}
	}
	if account.apiKeyAuthnCalls != 1 {
		t.Fatalf("APIKeyAuthn calls = %d, want 1", account.apiKeyAuthnCalls)
	}
}

func TestDispatcherDoesNotShareAPIKeyAuthenticationCacheAcrossCredentials(t *testing.T) {
	account := &fakeAccountProvider{apiKeyAuthnResults: []APIKeyAuthnResult{
		{PublicKey: "public-one", OrganizationID: 1},
		{PublicKey: "public-two", OrganizationID: 2},
	}}
	dispatcher := NewDispatcher(account, &fakeDefaultCredentials{credentials: tenant.CredentialProvisionRequest{PublicKey: "shared-public", PrivateKey: "shared-private"}, ok: true}, time.Hour)

	for _, caller := range []tenant.CredentialProvisionRequest{
		{PublicKey: "public-one", PrivateKey: "same-private"},
		{PublicKey: "public-two", PrivateKey: "same-private"},
	} {
		if _, err := dispatcher.Authenticate(context.Background(), "", 0, caller); err != nil {
			t.Fatalf("Authenticate %q: %v", caller.PublicKey, err)
		}
	}
	if account.apiKeyAuthnCalls != 2 {
		t.Fatalf("APIKeyAuthn calls = %d, want 2", account.apiKeyAuthnCalls)
	}
}

func TestDispatcherDoesNotCacheFailedAPIKeyAuthentication(t *testing.T) {
	account := &fakeAccountProvider{apiKeyAuthnErr: ErrUnauthenticated}
	dispatcher := NewDispatcher(account, nil, time.Hour)
	caller := tenant.CredentialProvisionRequest{PublicKey: "caller-public", PrivateKey: "caller-private"}

	for i := 0; i < 2; i++ {
		_, err := dispatcher.Authenticate(context.Background(), "", 0, caller)
		if !errors.Is(err, ErrUnauthenticated) {
			t.Fatalf("Authenticate call %d error = %v, want errors.Is(..., %v)", i+1, err, ErrUnauthenticated)
		}
	}
	if account.apiKeyAuthnCalls != 2 {
		t.Fatalf("APIKeyAuthn calls = %d, want 2", account.apiKeyAuthnCalls)
	}
}

func TestDispatcherRejectsMissingJWTDefaultCredentials(t *testing.T) {
	account := &fakeAccountProvider{userAuthnResult: UserAuthnResult{UserID: 123}}
	dispatcher := NewDispatcher(account, &fakeDefaultCredentials{}, time.Hour)
	_, err := dispatcher.Authenticate(context.Background(), "Bearer "+makeJWT(`{"sub":"user"}`), 60205, tenant.CredentialProvisionRequest{})
	if !errors.Is(err, ErrDefaultCredentialsUnavailable) {
		t.Fatalf("error = %v, want errors.Is(..., %v)", err, ErrDefaultCredentialsUnavailable)
	}
	if account.gotJWT == "" {
		t.Fatal("JWT was not authenticated before execution credentials were resolved")
	}
}

func TestDispatcherDisabledWithoutAccountProvider(t *testing.T) {
	dispatcher := NewDispatcher(nil, &fakeDefaultCredentials{}, time.Hour)
	if dispatcher.Enabled() {
		t.Fatal("dispatcher enabled with nil account provider")
	}
	_, err := dispatcher.Authenticate(context.Background(), "Bearer "+makeJWT(`{"sub":"user"}`), 60205, tenant.CredentialProvisionRequest{})
	if !errors.Is(err, ErrInnerAuthDisabled) {
		t.Fatalf("error = %v, want errors.Is(..., %v)", err, ErrInnerAuthDisabled)
	}
}

func TestDispatcherAuthorizesSubjectByCredentialKind(t *testing.T) {
	tests := []struct {
		name          string
		subject       ControlPlaneSubject
		wantUserID    uint64
		wantPublicKey string
	}{
		{name: "JWT user", subject: ControlPlaneSubject{Kind: CredentialJWT, UserID: 123}, wantUserID: 123},
		{name: "API key", subject: ControlPlaneSubject{Kind: CredentialAPIKey, PublicKey: "public"}, wantPublicKey: "public"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			account := &fakeAccountProvider{}
			dispatcher := NewDispatcher(account, nil, time.Hour)
			if err := dispatcher.Authorize(context.Background(), tt.subject, "/v1/tenants/42", "/v1/tenants/{id}"); err != nil {
				t.Fatalf("Authorize: %v", err)
			}
			if account.gotUserAuthzID != tt.wantUserID || account.gotAPIKeyAuthzPublicKey != tt.wantPublicKey {
				t.Fatalf("authz subject = (user %d, public key %q), want (user %d, public key %q)", account.gotUserAuthzID, account.gotAPIKeyAuthzPublicKey, tt.wantUserID, tt.wantPublicKey)
			}
			if account.gotRequestURI != "/v1/tenants/42" || account.gotResourceURI != "/v1/tenants/{id}" {
				t.Fatalf("authz URIs = (%q, %q)", account.gotRequestURI, account.gotResourceURI)
			}
		})
	}
}

func TestDispatcherCachesSuccessfulAPIKeyAuthorization(t *testing.T) {
	account := &fakeAccountProvider{}
	dispatcher := NewDispatcher(account, nil, time.Hour)
	subject := ControlPlaneSubject{Kind: CredentialAPIKey, PublicKey: "public"}

	for i := 0; i < 2; i++ {
		if err := dispatcher.Authorize(context.Background(), subject, "/v1/tenants/42", "/v1/tenants/{id}"); err != nil {
			t.Fatalf("Authorize call %d: %v", i+1, err)
		}
	}
	if account.apiKeyAuthzCalls != 1 {
		t.Fatalf("APIKeyAuthz calls = %d, want 1", account.apiKeyAuthzCalls)
	}
}

func TestDispatcherScopesAPIKeyAuthorizationCacheBySubjectAndURIs(t *testing.T) {
	account := &fakeAccountProvider{}
	dispatcher := NewDispatcher(account, nil, time.Hour)
	calls := []struct {
		publicKey   string
		requestURI  string
		resourceURI string
	}{
		{publicKey: "public-one", requestURI: "/v1/tenants/1", resourceURI: "/v1/tenants/{id}"},
		{publicKey: "public-two", requestURI: "/v1/tenants/1", resourceURI: "/v1/tenants/{id}"},
		{publicKey: "public-one", requestURI: "/v1/tenants/2", resourceURI: "/v1/tenants/{id}"},
		{publicKey: "public-one", requestURI: "/v1/tenants/1", resourceURI: "/v1/organizations/{id}"},
	}
	for _, call := range calls {
		subject := ControlPlaneSubject{Kind: CredentialAPIKey, PublicKey: call.publicKey}
		if err := dispatcher.Authorize(context.Background(), subject, call.requestURI, call.resourceURI); err != nil {
			t.Fatalf("Authorize (%q, %q, %q): %v", call.publicKey, call.requestURI, call.resourceURI, err)
		}
	}
	if account.apiKeyAuthzCalls != len(calls) {
		t.Fatalf("APIKeyAuthz calls = %d, want %d", account.apiKeyAuthzCalls, len(calls))
	}
}

func TestDispatcherDoesNotCacheFailedAPIKeyAuthorization(t *testing.T) {
	account := &fakeAccountProvider{apiKeyAuthzErr: ErrForbidden}
	dispatcher := NewDispatcher(account, nil, time.Hour)
	subject := ControlPlaneSubject{Kind: CredentialAPIKey, PublicKey: "public"}

	for i := 0; i < 2; i++ {
		err := dispatcher.Authorize(context.Background(), subject, "/v1/tenants/42", "/v1/tenants/{id}")
		if !errors.Is(err, ErrForbidden) {
			t.Fatalf("Authorize call %d error = %v, want errors.Is(..., %v)", i+1, err, ErrForbidden)
		}
	}
	if account.apiKeyAuthzCalls != 2 {
		t.Fatalf("APIKeyAuthz calls = %d, want 2", account.apiKeyAuthzCalls)
	}
}

func TestDispatcherDoesNotCacheJWTAuthenticationOrAuthorization(t *testing.T) {
	account := &fakeAccountProvider{userAuthnResult: UserAuthnResult{UserID: 123}}
	defaults := &fakeDefaultCredentials{credentials: tenant.CredentialProvisionRequest{PublicKey: "server-public", PrivateKey: "server-private"}, ok: true}
	dispatcher := NewDispatcher(account, defaults, time.Hour)
	header := "Bearer " + makeJWT(`{"sub":"user"}`)

	for i := 0; i < 2; i++ {
		subject, err := dispatcher.Authenticate(context.Background(), header, 60205, tenant.CredentialProvisionRequest{})
		if err != nil {
			t.Fatalf("Authenticate call %d: %v", i+1, err)
		}
		if err := dispatcher.Authorize(context.Background(), subject, "/v1/tenants/42", "/v1/tenants/{id}"); err != nil {
			t.Fatalf("Authorize call %d: %v", i+1, err)
		}
	}
	if account.userAuthnCalls != 2 || account.userAuthzCalls != 2 {
		t.Fatalf("JWT calls = (authn %d, authz %d), want (2, 2)", account.userAuthnCalls, account.userAuthzCalls)
	}
}

func TestDispatcherRecordsInnerIAMMetricsWithCacheOutcome(t *testing.T) {
	account := &fakeAccountProvider{apiKeyAuthnResult: APIKeyAuthnResult{PublicKey: "metrics-public", OrganizationID: 60205}}
	dispatcher := NewDispatcher(account, &fakeDefaultCredentials{credentials: tenant.CredentialProvisionRequest{PublicKey: "shared-public", PrivateKey: "shared-private"}, ok: true}, time.Hour)
	caller := tenant.CredentialProvisionRequest{PublicKey: "metrics-public", PrivateKey: "metrics-private"}

	for i := 0; i < 2; i++ {
		subject, err := dispatcher.Authenticate(context.Background(), "", 0, caller)
		if err != nil {
			t.Fatalf("Authenticate call %d: %v", i+1, err)
		}
		if err := dispatcher.Authorize(context.Background(), subject, "/drive9/orgs/60205/quota/view", "/drive9/orgs/{org_id}/quota/view"); err != nil {
			t.Fatalf("Authorize call %d: %v", i+1, err)
		}
	}

	recorder := httptest.NewRecorder()
	metrics.WritePrometheus(recorder)
	text := recorder.Body.String()
	for _, want := range []string{
		`drive9_tidbcloud_inner_auth_cache_requests_total{operation="api_key_authn",result="miss"}`,
		`drive9_tidbcloud_inner_auth_cache_requests_total{operation="api_key_authn",result="hit"}`,
		`drive9_tidbcloud_inner_auth_cache_requests_total{operation="api_key_authz",result="miss"}`,
		`drive9_tidbcloud_inner_auth_cache_requests_total{operation="api_key_authz",result="hit"}`,
	} {
		if !strings.Contains(text, want) {
			t.Fatalf("missing inner IAM metric %q:\n%s", want, text)
		}
	}
}

type fakeAccountProvider struct {
	userAuthnResult         UserAuthnResult
	apiKeyAuthnResult       APIKeyAuthnResult
	apiKeyAuthnResults      []APIKeyAuthnResult
	apiKeyAuthnErr          error
	apiKeyAuthzErr          error
	userAuthnCalls          int
	userAuthzCalls          int
	apiKeyAuthnCalls        int
	apiKeyAuthzCalls        int
	gotJWT                  string
	gotAPIKey               string
	gotUserAuthzID          uint64
	gotAPIKeyAuthzPublicKey string
	gotRequestURI           string
	gotResourceURI          string
}

func (f *fakeAccountProvider) UserAuthn(_ context.Context, jwt string) (UserAuthnResult, error) {
	f.userAuthnCalls++
	f.gotJWT = jwt
	return f.userAuthnResult, nil
}

func (f *fakeAccountProvider) UserAuthz(_ context.Context, userID uint64, requestURI, resourceURI string) error {
	f.userAuthzCalls++
	f.gotUserAuthzID = userID
	f.gotRequestURI = requestURI
	f.gotResourceURI = resourceURI
	return nil
}

func (f *fakeAccountProvider) APIKeyAuthn(_ context.Context, encoded string) (APIKeyAuthnResult, error) {
	f.apiKeyAuthnCalls++
	f.gotAPIKey = encoded
	if f.apiKeyAuthnErr != nil {
		return APIKeyAuthnResult{}, f.apiKeyAuthnErr
	}
	if len(f.apiKeyAuthnResults) >= f.apiKeyAuthnCalls {
		return f.apiKeyAuthnResults[f.apiKeyAuthnCalls-1], nil
	}
	return f.apiKeyAuthnResult, nil
}

func (f *fakeAccountProvider) APIKeyAuthz(_ context.Context, publicKey, requestURI, resourceURI string) error {
	f.apiKeyAuthzCalls++
	f.gotAPIKeyAuthzPublicKey = publicKey
	f.gotRequestURI = requestURI
	f.gotResourceURI = resourceURI
	return f.apiKeyAuthzErr
}

type fakeDefaultCredentials struct {
	credentials tenant.CredentialProvisionRequest
	ok          bool
}

func (f *fakeDefaultCredentials) DefaultCredentials() (tenant.CredentialProvisionRequest, bool) {
	return f.credentials, f.ok
}
