package middleware

import (
	"context"
	"errors"
	"sync"
	"testing"
	"time"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
)

type permissionStateProviderFunc func(
	context.Context,
	permissionstatev1.Subject,
) (permissionstatev1.EffectiveSnapshot, error)

func (function permissionStateProviderFunc) LoadCurrentEffectivePermissionSnapshot(
	ctx context.Context,
	subject permissionstatev1.Subject,
) (permissionstatev1.EffectiveSnapshot, error) {
	return function(ctx, subject)
}

func TestPermissionStateCacheReusesSnapshotThatSatisfiesFloor(t *testing.T) {
	subject := testPermissionStateSubject(t)
	version := testPermissionStateVersion(t, 1, 4)
	snapshot := testPermissionStateSnapshot(t, version, "ApiEnabled")
	calls := 0
	provider := permissionStateProviderFunc(func(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error) {
		calls++
		return snapshot, nil
	})
	cache := newTestPermissionStateCache(t)

	for range 2 {
		got, err := cache.Resolve(context.Background(), provider, subject, version)
		if err != nil {
			t.Fatalf("Resolve: %v", err)
		}
		if !got.HasSystemPermission("ApiEnabled") {
			t.Fatal("resolved snapshot omitted system permission")
		}
	}
	if calls != 1 {
		t.Fatalf("provider calls = %d, want 1", calls)
	}
}

func TestPermissionStateCacheReloadsForHigherFloor(t *testing.T) {
	subject := testPermissionStateSubject(t)
	version1 := testPermissionStateVersion(t, 1, 1)
	version2 := testPermissionStateVersion(t, 1, 2)
	snapshots := []permissionstatev1.EffectiveSnapshot{
		testPermissionStateSnapshot(t, version1, "ApiEnabled"),
		testPermissionStateSnapshot(t, version2, "ApiEnabled", "ModifyAllData"),
	}
	calls := 0
	provider := permissionStateProviderFunc(func(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error) {
		result := snapshots[calls]
		calls++
		return result, nil
	})
	cache := newTestPermissionStateCache(t)

	if _, err := cache.Resolve(context.Background(), provider, subject, version1); err != nil {
		t.Fatalf("Resolve(v1): %v", err)
	}
	got, err := cache.Resolve(context.Background(), provider, subject, version2)
	if err != nil {
		t.Fatalf("Resolve(v2): %v", err)
	}
	if got.Version().Revision() != 2 || calls != 2 {
		t.Fatalf("higher-floor result revision=%d calls=%d, want 2/2", got.Version().Revision(), calls)
	}
}

func TestPermissionStateCacheHigherFloorFollowerRetriesBoundedly(t *testing.T) {
	subject := testPermissionStateSubject(t)
	version1 := testPermissionStateVersion(t, 1, 1)
	version2 := testPermissionStateVersion(t, 1, 2)
	snapshot1 := testPermissionStateSnapshot(t, version1, "ApiEnabled")
	snapshot2 := testPermissionStateSnapshot(t, version2, "ApiEnabled", "ModifyAllData")
	firstStarted := make(chan struct{})
	releaseFirst := make(chan struct{})
	var mu sync.Mutex
	calls := 0
	provider := permissionStateProviderFunc(func(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error) {
		mu.Lock()
		calls++
		call := calls
		mu.Unlock()
		if call == 1 {
			close(firstStarted)
			<-releaseFirst
			return snapshot1, nil
		}
		return snapshot2, nil
	})
	cache := newTestPermissionStateCache(t)
	lowResult := make(chan error, 1)
	go func() {
		_, err := cache.Resolve(context.Background(), provider, subject, version1)
		lowResult <- err
	}()
	<-firstStarted
	highResult := make(chan permissionstatev1.EffectiveSnapshot, 1)
	highError := make(chan error, 1)
	go func() {
		snapshot, err := cache.Resolve(context.Background(), provider, subject, version2)
		highResult <- snapshot
		highError <- err
	}()
	close(releaseFirst)

	if err := <-lowResult; err != nil {
		t.Fatalf("lower-floor leader: %v", err)
	}
	if err := <-highError; err != nil {
		t.Fatalf("higher-floor follower: %v", err)
	}
	if got := <-highResult; got.Version().Revision() != 2 {
		t.Fatalf("higher-floor revision = %d, want 2", got.Version().Revision())
	}
	mu.Lock()
	defer mu.Unlock()
	if calls != 2 {
		t.Fatalf("provider calls = %d, want bounded 2", calls)
	}
}

func TestPermissionStateCacheLineageSwitchFencesOldLoadAndRetiresLineage(t *testing.T) {
	subject := testPermissionStateSubject(t)
	versionA := testPermissionStateVersion(t, 1, 1)
	versionB := testPermissionStateVersion(t, 2, 1)
	snapshotA := testPermissionStateSnapshot(t, versionA, "OldPermission")
	snapshotB := testPermissionStateSnapshot(t, versionB, "NewPermission")
	firstStarted := make(chan struct{})
	releaseFirst := make(chan struct{})
	var mu sync.Mutex
	calls := 0
	provider := permissionStateProviderFunc(func(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error) {
		mu.Lock()
		calls++
		call := calls
		mu.Unlock()
		if call == 1 {
			close(firstStarted)
			<-releaseFirst
			return snapshotA, nil
		}
		return snapshotB, nil
	})
	cache := newTestPermissionStateCache(t)
	oldResult := make(chan error, 1)
	go func() {
		_, err := cache.Resolve(context.Background(), provider, subject, versionA)
		oldResult <- err
	}()
	<-firstStarted

	got, err := cache.Resolve(context.Background(), provider, subject, versionB)
	if err != nil {
		t.Fatalf("Resolve(new lineage): %v", err)
	}
	if !got.HasSystemPermission("NewPermission") {
		t.Fatal("new-lineage snapshot was not published")
	}
	close(releaseFirst)
	assertPermissionStateFailure(
		t,
		<-oldResult,
		permissionstatev1.FailureIncomparableLineage,
	)
	_, err = cache.Resolve(context.Background(), provider, subject, versionA)
	assertPermissionStateFailure(t, err, permissionstatev1.FailureIncomparableLineage)
}

func TestPermissionStateCacheLineageFenceIsGlobalAcrossSubjects(t *testing.T) {
	subjectA := testPermissionStateSubject(t)
	subjectB, err := permissionstatev1.NewSubject("user-2", "profile-2")
	if err != nil {
		t.Fatal(err)
	}
	versionA := testPermissionStateVersion(t, 1, 1)
	versionB := testPermissionStateVersion(t, 2, 1)
	snapshots := []permissionstatev1.EffectiveSnapshot{
		testPermissionStateSnapshot(t, versionA, "OldPermission"),
		testPermissionStateSnapshot(t, versionB, "NewPermission"),
	}
	calls := 0
	provider := permissionStateProviderFunc(func(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error) {
		result := snapshots[calls]
		calls++
		return result, nil
	})
	cache := newTestPermissionStateCache(t)
	if _, err := cache.Resolve(context.Background(), provider, subjectA, versionA); err != nil {
		t.Fatalf("Resolve(old lineage): %v", err)
	}
	if _, err := cache.Resolve(context.Background(), provider, subjectB, versionB); err != nil {
		t.Fatalf("Resolve(new lineage): %v", err)
	}
	_, err = cache.Resolve(context.Background(), provider, subjectA, versionA)
	assertPermissionStateFailure(t, err, permissionstatev1.FailureIncomparableLineage)
	if calls != 2 {
		t.Fatalf("provider calls = %d, want old lineage rejected before a third load", calls)
	}
}

func TestPermissionStateCacheTTLExpiryCannotDowngradeRevision(t *testing.T) {
	subject := testPermissionStateSubject(t)
	version1 := testPermissionStateVersion(t, 1, 1)
	version2 := testPermissionStateVersion(t, 1, 2)
	snapshot1 := testPermissionStateSnapshot(t, version1, "ApiEnabled")
	snapshot2 := testPermissionStateSnapshot(t, version2, "ApiEnabled", "ModifyAllData")
	now := time.Unix(100, 0)
	calls := 0
	provider := permissionStateProviderFunc(func(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error) {
		calls++
		if calls == 1 {
			return snapshot2, nil
		}
		return snapshot1, nil
	})
	cache := newTestPermissionStateCache(t)
	cache.now = func() time.Time { return now }

	if _, err := cache.Resolve(context.Background(), provider, subject, version1); err != nil {
		t.Fatalf("Resolve(v2): %v", err)
	}
	now = now.Add(cache.ttl + time.Second)
	_, err := cache.Resolve(context.Background(), provider, subject, version1)
	assertPermissionStateFailure(t, err, permissionstatev1.FailureStaleSnapshot)
}

func TestPermissionStateCacheRejectsDifferentSemanticsAtSameVersion(t *testing.T) {
	subject := testPermissionStateSubject(t)
	version := testPermissionStateVersion(t, 1, 1)
	snapshots := []permissionstatev1.EffectiveSnapshot{
		testPermissionStateSnapshot(t, version, "ApiEnabled"),
		testPermissionStateSnapshot(t, version, "ModifyAllData"),
	}
	now := time.Unix(100, 0)
	calls := 0
	provider := permissionStateProviderFunc(func(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error) {
		result := snapshots[calls]
		calls++
		return result, nil
	})
	cache := newTestPermissionStateCache(t)
	cache.now = func() time.Time { return now }
	if _, err := cache.Resolve(context.Background(), provider, subject, version); err != nil {
		t.Fatalf("Resolve(first): %v", err)
	}
	now = now.Add(cache.ttl + time.Second)
	_, err := cache.Resolve(context.Background(), provider, subject, version)
	assertPermissionStateFailure(t, err, permissionstatev1.FailureAuthorityUnavailable)
}

func TestPermissionStateCacheWarmSeedsResolveFromOneAggregateLoad(t *testing.T) {
	subject := testPermissionStateSubject(t)
	version := testPermissionStateVersion(t, 1, 7)
	snapshot := testPermissionStateSnapshot(t, version, "ApiEnabled")
	calls := 0
	provider := permissionStateProviderFunc(func(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error) {
		calls++
		return snapshot, nil
	})
	cache := newTestPermissionStateCache(t)

	if err := cache.Warm(context.Background(), provider, subject); err != nil {
		t.Fatalf("Warm: %v", err)
	}
	got, err := cache.Resolve(context.Background(), provider, subject, version)
	if err != nil {
		t.Fatalf("Resolve: %v", err)
	}
	if calls != 1 || !got.HasSystemPermission("ApiEnabled") {
		t.Fatalf("calls=%d snapshot=%#v, want one warmed load", calls, got)
	}
}

func TestPermissionStateCacheResolveReusesNewerConcurrentWarm(t *testing.T) {
	subject := testPermissionStateSubject(t)
	version1 := testPermissionStateVersion(t, 1, 1)
	version2 := testPermissionStateVersion(t, 1, 2)
	resolveStarted := make(chan struct{})
	releaseResolve := make(chan struct{})
	var mu sync.Mutex
	calls := 0
	provider := permissionStateProviderFunc(func(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error) {
		mu.Lock()
		calls++
		call := calls
		mu.Unlock()
		if call == 1 {
			close(resolveStarted)
			<-releaseResolve
			return testPermissionStateSnapshot(t, version1, "Old"), nil
		}
		return testPermissionStateSnapshot(t, version2, "Current"), nil
	})
	cache := newTestPermissionStateCache(t)
	type resolveResult struct {
		snapshot permissionstatev1.EffectiveSnapshot
		err      error
	}
	result := make(chan resolveResult, 1)
	go func() {
		snapshot, err := cache.Resolve(
			context.Background(),
			provider,
			subject,
			version1,
		)
		result <- resolveResult{snapshot: snapshot, err: err}
	}()
	<-resolveStarted
	if err := cache.Warm(context.Background(), provider, subject); err != nil {
		t.Fatalf("Warm newer snapshot: %v", err)
	}
	close(releaseResolve)

	resolved := <-result
	if resolved.err != nil {
		t.Fatalf("Resolve returned stale publication failure: %v", resolved.err)
	}
	if resolved.snapshot.Version().Revision() != 2 ||
		!resolved.snapshot.HasSystemPermission("Current") {
		t.Fatalf("Resolve snapshot = %#v, want concurrent warm revision 2", resolved.snapshot)
	}
	mu.Lock()
	defer mu.Unlock()
	if calls != 2 {
		t.Fatalf("provider calls = %d, want one Resolve and one Warm", calls)
	}
}

func TestPermissionStateCacheWarmCannotDowngradeOrReviveLineage(t *testing.T) {
	subject := testPermissionStateSubject(t)
	versionA1 := testPermissionStateVersion(t, 1, 1)
	versionA2 := testPermissionStateVersion(t, 1, 2)
	versionB := testPermissionStateVersion(t, 2, 1)
	cache := newTestPermissionStateCache(t)
	now := time.Unix(100, 0)
	cache.now = func() time.Time { return now }

	current := testPermissionStateSnapshot(t, versionA2, "Current")
	if _, err := cache.Resolve(
		context.Background(),
		permissionStateProviderFunc(func(
			context.Context,
			permissionstatev1.Subject,
		) (permissionstatev1.EffectiveSnapshot, error) {
			return current, nil
		}),
		subject,
		versionA1,
	); err != nil {
		t.Fatalf("Resolve(current): %v", err)
	}
	now = now.Add(cache.ttl + time.Second)
	err := cache.Warm(
		context.Background(),
		permissionStateProviderFunc(func(
			context.Context,
			permissionstatev1.Subject,
		) (permissionstatev1.EffectiveSnapshot, error) {
			return testPermissionStateSnapshot(t, versionA1, "Old"), nil
		}),
		subject,
	)
	assertPermissionStateFailure(t, err, permissionstatev1.FailureStaleSnapshot)

	if _, err := cache.Resolve(
		context.Background(),
		permissionStateProviderFunc(func(
			context.Context,
			permissionstatev1.Subject,
		) (permissionstatev1.EffectiveSnapshot, error) {
			return testPermissionStateSnapshot(t, versionB, "Restored"), nil
		}),
		subject,
		versionB,
	); err != nil {
		t.Fatalf("Resolve(restored lineage): %v", err)
	}
	err = cache.Warm(
		context.Background(),
		permissionStateProviderFunc(func(
			context.Context,
			permissionstatev1.Subject,
		) (permissionstatev1.EffectiveSnapshot, error) {
			return current, nil
		}),
		subject,
	)
	assertPermissionStateFailure(
		t,
		err,
		permissionstatev1.FailureIncomparableLineage,
	)
}

func TestPermissionStateCacheWarmChecksSameVersionSemanticsOnCacheHit(
	t *testing.T,
) {
	subject := testPermissionStateSubject(t)
	version := testPermissionStateVersion(t, 1, 4)
	cache := newTestPermissionStateCache(t)
	if _, err := cache.Resolve(
		context.Background(),
		permissionStateProviderFunc(func(
			context.Context,
			permissionstatev1.Subject,
		) (permissionstatev1.EffectiveSnapshot, error) {
			return testPermissionStateSnapshot(t, version, "ApiEnabled"), nil
		}),
		subject,
		version,
	); err != nil {
		t.Fatalf("Resolve: %v", err)
	}

	err := cache.Warm(
		context.Background(),
		permissionStateProviderFunc(func(
			context.Context,
			permissionstatev1.Subject,
		) (permissionstatev1.EffectiveSnapshot, error) {
			return testPermissionStateSnapshot(t, version, "ModifyAllData"), nil
		}),
		subject,
	)
	assertPermissionStateFailure(
		t,
		err,
		permissionstatev1.FailureAuthorityUnavailable,
	)
}

func TestPermissionStateCacheInvalidateFencesInFlightResolve(t *testing.T) {
	subject := testPermissionStateSubject(t)
	version1 := testPermissionStateVersion(t, 1, 1)
	version2 := testPermissionStateVersion(t, 1, 2)
	started := make(chan struct{})
	release := make(chan struct{})
	calls := 0
	provider := permissionStateProviderFunc(func(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error) {
		calls++
		if calls == 1 {
			close(started)
			<-release
			return testPermissionStateSnapshot(t, version1, "Old"), nil
		}
		return testPermissionStateSnapshot(t, version2, "Current"), nil
	})
	cache := newTestPermissionStateCache(t)
	result := make(chan error, 1)
	go func() {
		_, err := cache.Resolve(
			context.Background(),
			provider,
			subject,
			version1,
		)
		result <- err
	}()
	<-started
	cache.InvalidateUser(subject.UserID())
	close(release)
	assertPermissionStateFailure(
		t,
		<-result,
		permissionstatev1.FailureStaleSnapshot,
	)

	got, err := cache.Resolve(
		context.Background(),
		provider,
		subject,
		version1,
	)
	if err != nil {
		t.Fatalf("Resolve after invalidation: %v", err)
	}
	if got.Version().Revision() != 2 || calls != 2 {
		t.Fatalf(
			"post-invalidation revision=%d calls=%d, want 2/2",
			got.Version().Revision(),
			calls,
		)
	}
}

func TestPermissionStateCacheInvalidateUserDoesNotFenceOtherUserInFlightResolve(
	t *testing.T,
) {
	subjectA := testPermissionStateSubject(t)
	subjectB, err := permissionstatev1.NewSubject("user-2", "profile-2")
	if err != nil {
		t.Fatal(err)
	}
	version := testPermissionStateVersion(t, 1, 1)
	snapshot := testPermissionStateSnapshot(t, version, "Current")
	started := make(chan struct{})
	release := make(chan struct{})
	calls := 0
	provider := permissionStateProviderFunc(func(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error) {
		calls++
		if calls == 1 {
			close(started)
			<-release
		}
		return snapshot, nil
	})
	cache := newTestPermissionStateCache(t)
	result := make(chan error, 1)
	go func() {
		_, err := cache.Resolve(
			context.Background(),
			provider,
			subjectA,
			version,
		)
		result <- err
	}()
	<-started
	cache.InvalidateUser(subjectB.UserID())
	close(release)
	if err := <-result; err != nil {
		t.Fatalf("Resolve for unrelated user was fenced: %v", err)
	}

	if _, err := cache.Resolve(
		context.Background(),
		provider,
		subjectA,
		version,
	); err != nil {
		t.Fatalf("Resolve cached unrelated-user result: %v", err)
	}
	if calls != 1 {
		t.Fatalf("provider calls = %d, want unrelated load published once", calls)
	}
}

func TestPermissionStateCacheInvalidateFencesInFlightWarm(t *testing.T) {
	subject := testPermissionStateSubject(t)
	version1 := testPermissionStateVersion(t, 1, 1)
	version2 := testPermissionStateVersion(t, 1, 2)
	started := make(chan struct{})
	release := make(chan struct{})
	calls := 0
	provider := permissionStateProviderFunc(func(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error) {
		calls++
		if calls == 1 {
			close(started)
			<-release
			return testPermissionStateSnapshot(t, version1, "Old"), nil
		}
		return testPermissionStateSnapshot(t, version2, "Current"), nil
	})
	cache := newTestPermissionStateCache(t)
	result := make(chan error, 1)
	go func() {
		result <- cache.Warm(context.Background(), provider, subject)
	}()
	<-started
	cache.Invalidate()
	close(release)
	assertPermissionStateFailure(
		t,
		<-result,
		permissionstatev1.FailureStaleSnapshot,
	)

	got, err := cache.Resolve(
		context.Background(),
		provider,
		subject,
		version1,
	)
	if err != nil {
		t.Fatalf("Resolve after invalidated warm: %v", err)
	}
	if got.Version().Revision() != 2 || calls != 2 {
		t.Fatalf(
			"post-warm revision=%d calls=%d, want 2/2",
			got.Version().Revision(),
			calls,
		)
	}
}

func TestPermissionStateCacheInvalidationRetainsPublicationHighWatermark(t *testing.T) {
	subject := testPermissionStateSubject(t)
	version1 := testPermissionStateVersion(t, 1, 1)
	version2 := testPermissionStateVersion(t, 1, 2)
	for _, test := range []struct {
		name       string
		invalidate func(*PermissionStateCache)
	}{
		{name: "user", invalidate: func(cache *PermissionStateCache) {
			cache.InvalidateUser(subject.UserID())
		}},
		{name: "global", invalidate: func(cache *PermissionStateCache) {
			cache.Invalidate()
		}},
	} {
		t.Run(test.name, func(t *testing.T) {
			cache := newTestPermissionStateCache(t)
			if err := cache.Warm(
				context.Background(),
				permissionStateProviderFunc(func(
					context.Context,
					permissionstatev1.Subject,
				) (permissionstatev1.EffectiveSnapshot, error) {
					return testPermissionStateSnapshot(t, version2, "Current"), nil
				}),
				subject,
			); err != nil {
				t.Fatalf("Warm(current): %v", err)
			}
			test.invalidate(cache)

			err := cache.Warm(
				context.Background(),
				permissionStateProviderFunc(func(
					context.Context,
					permissionstatev1.Subject,
				) (permissionstatev1.EffectiveSnapshot, error) {
					return testPermissionStateSnapshot(t, version1, "Old"), nil
				}),
				subject,
			)
			assertPermissionStateFailure(t, err, permissionstatev1.FailureStaleSnapshot)

			err = cache.Warm(
				context.Background(),
				permissionStateProviderFunc(func(
					context.Context,
					permissionstatev1.Subject,
				) (permissionstatev1.EffectiveSnapshot, error) {
					return testPermissionStateSnapshot(t, version2, "Conflicting"), nil
				}),
				subject,
			)
			assertPermissionStateFailure(
				t,
				err,
				permissionstatev1.FailureAuthorityUnavailable,
			)
		})
	}
}

func TestPermissionStateCacheInvalidationIsSubjectScopedAndPreservesFloor(t *testing.T) {
	subject1 := testPermissionStateSubject(t)
	subject2, err := permissionstatev1.NewSubject("user-10", "profile-2")
	if err != nil {
		t.Fatal(err)
	}
	version := testPermissionStateVersion(t, 1, 3)
	calls := map[string]int{}
	provider := permissionStateProviderFunc(func(
		_ context.Context,
		subject permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error) {
		calls[subject.UserID()]++
		return testPermissionStateSnapshot(
			t,
			version,
			subject.UserID()+"Permission",
		), nil
	})
	cache := newTestPermissionStateCache(t)
	for _, subject := range []permissionstatev1.Subject{subject1, subject2} {
		if _, err := cache.Resolve(
			context.Background(),
			provider,
			subject,
			version,
		); err != nil {
			t.Fatal(err)
		}
	}

	cache.InvalidateUser(subject1.UserID())
	for _, subject := range []permissionstatev1.Subject{subject1, subject2} {
		if _, err := cache.Resolve(
			context.Background(),
			provider,
			subject,
			version,
		); err != nil {
			t.Fatal(err)
		}
	}
	if calls[subject1.UserID()] != 2 || calls[subject2.UserID()] != 1 {
		t.Fatalf("subject invalidation calls = %#v", calls)
	}

	cache.Invalidate()
	for _, subject := range []permissionstatev1.Subject{subject1, subject2} {
		if _, err := cache.Resolve(
			context.Background(),
			provider,
			subject,
			version,
		); err != nil {
			t.Fatal(err)
		}
	}
	if calls[subject1.UserID()] != 3 || calls[subject2.UserID()] != 2 {
		t.Fatalf("global invalidation calls = %#v", calls)
	}
}

func newTestPermissionStateCache(t *testing.T) *PermissionStateCache {
	t.Helper()
	cache, err := NewPermissionStateCache(time.Minute)
	if err != nil {
		t.Fatal(err)
	}
	return cache
}

func testPermissionStateSubject(t *testing.T) permissionstatev1.Subject {
	t.Helper()
	subject, err := permissionstatev1.NewSubject("user-1", "profile-1")
	if err != nil {
		t.Fatal(err)
	}
	return subject
}

func testPermissionStateVersion(t *testing.T, lineageByte byte, revision uint64) permissionstatev1.Version {
	t.Helper()
	lineageBytes := make([]byte, permissionstatev1.LineageBytes)
	lineageBytes[0] = lineageByte
	lineage, err := permissionstatev1.LineageFromBytes(lineageBytes)
	if err != nil {
		t.Fatal(err)
	}
	version, err := permissionstatev1.NewVersion(lineage, revision)
	if err != nil {
		t.Fatal(err)
	}
	return version
}

func testPermissionStateSnapshot(
	t *testing.T,
	version permissionstatev1.Version,
	systemPermissions ...string,
) permissionstatev1.EffectiveSnapshot {
	t.Helper()
	snapshot, err := permissionstatev1.NewEffectiveSnapshot(version, systemPermissions, nil)
	if err != nil {
		t.Fatal(err)
	}
	return snapshot
}

func assertPermissionStateFailure(
	t *testing.T,
	err error,
	category permissionstatev1.FailureCategory,
) {
	t.Helper()
	var failure *permissionstatev1.Failure
	if !errors.As(err, &failure) || failure.Category != category {
		t.Fatalf("error = %v, want Permission State %s", err, category)
	}
}
