package automation

import (
	"context"
	"encoding/base64"
	"errors"
	"io"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/internal/automation/blob"
	automationstore "github.com/tidbcloud/AgentTeam/internal/store/automation"
	"gorm.io/gorm"
)

func TestAutomationRunnerLogBlobStorageIsIdempotentAndDetectsMissingBlob(t *testing.T) {
	ctx := context.Background()
	svc, store, blobStore, now := newAutomationBlobTestService(t)
	session := registerAutomationBlobTestRunner(t, ctx, svc, "ws-1", "runner-1", "session-1")
	job := createAutomationBlobTestJob(t, ctx, store, "ws-1", "run-1", "job-1", session.LeaseOwner, now())

	logPayload := "building"
	logSHA := blob.SHA256Hex([]byte(logPayload))
	event, err := svc.AppendAutomationRunnerJobLog(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, LogStream: "stdout", LogContent: logPayload, LogSequence: 1, LogSHA256: logSHA})
	require.NoError(t, err)
	require.Contains(t, event.MetadataJSON, `"object_key"`)
	require.Contains(t, event.MetadataJSON, logSHA)
	require.NotContains(t, event.MetadataJSON, logPayload)

	retried, err := svc.AppendAutomationRunnerJobLog(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, LogStream: "stdout", LogContent: logPayload, LogSequence: 1, LogSHA256: logSHA})
	require.NoError(t, err)
	require.Equal(t, event.ID, retried.ID)

	_, err = svc.AppendAutomationRunnerJobLog(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, LogStream: "stdout", LogContent: logPayload, LogSequence: 1, LogSHA256: logSHA, LogContentType: "application/x-log"})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "sequence already exists")

	chunks, err := store.ListLogChunks(ctx, "ws-1", "run-1", "job-1", "stdout", 1, 30)
	require.NoError(t, err)
	require.Len(t, chunks, 1)
	content, err := svc.ReadAutomationLogChunkContent(ctx, "ws-1", "user-1", "run-1", "job-1", "stdout", 1)
	require.NoError(t, err)
	require.Equal(t, logPayload, content.Content)

	require.NoError(t, blobStore.Delete(ctx, chunks[0].ObjectKey))
	_, err = svc.ReadAutomationLogChunkContent(ctx, "ws-1", "user-1", "run-1", "job-1", "stdout", 1)
	require.ErrorIs(t, err, ErrNotFound)
	require.Contains(t, err.Error(), "blob object is missing")

	_, err = svc.AppendAutomationRunnerJobLog(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, LogStream: "stdout", LogContent: "different", LogSequence: 1})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "different content")
}

func TestAutomationLogChunkContentAuthorizesConcreteResourceBeforeBlobRead(t *testing.T) {
	ctx := context.Background()
	svc, store, blobStore, now := newAutomationBlobTestService(t)
	countingStore := &countingAutomationBlobStore{Store: blobStore}
	svc.SetBlobStore(countingStore)
	session := registerAutomationBlobTestRunner(t, ctx, svc, "ws-1", "runner-1", "session-1")
	job := createAutomationBlobTestJob(t, ctx, store, "ws-1", "run-1", "job-1", session.LeaseOwner, now())

	payload := "building"
	_, err := svc.AppendAutomationRunnerJobLog(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, LogStream: "stdout", LogContent: payload, LogSequence: 1, LogSHA256: blob.SHA256Hex([]byte(payload))})
	require.NoError(t, err)
	countingStore.gets = 0

	authorizer := &automationBlobContentReadAuthorizer{
		deny: func(request AutomationAuthorizationRequest) bool {
			return request.Run.ID == "run-1" &&
				request.JobRunID == "job-1" &&
				request.LogStream == "stdout" &&
				request.LogSequence == 1
		},
	}
	svc.SetAuthorizer(authorizer)

	_, err = svc.ReadAutomationLogChunkContent(ctx, "ws-1", "user-1", "run-1", "job-1", "stdout", 1)
	require.ErrorIs(t, err, ErrForbidden)
	require.Zero(t, countingStore.gets)
	require.True(t, authorizer.saw(func(request AutomationAuthorizationRequest) bool {
		return request.Run.ID == "run-1" &&
			request.JobRunID == "job-1" &&
			request.LogStream == "stdout" &&
			request.LogSequence == 1
	}), "CanReadRun did not receive the concrete log content scope")
}

func TestAutomationRunnerLogBlobStorageRejectsStaleLeaseBeforeBlobWrite(t *testing.T) {
	ctx := context.Background()
	svc, store, blobStore, now := newAutomationBlobTestService(t)
	session := registerAutomationBlobTestRunner(t, ctx, svc, "ws-1", "runner-1", "session-1")
	job := createAutomationBlobTestJobWithLeaseUntil(t, ctx, store, "ws-1", "run-1", "job-1", session.LeaseOwner, now(), now().Add(-time.Second))
	objectKey, err := automationLogChunkObjectKey("ws-1", "run-1", "job-1", "stdout", 1)
	require.NoError(t, err)

	_, err = svc.AppendAutomationRunnerJobLog(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, LogStream: "stdout", LogContent: "late", LogSequence: 1})
	require.ErrorIs(t, err, ErrNotFound)
	_, err = blobStore.Get(ctx, objectKey)
	require.ErrorIs(t, err, blob.ErrNotFound)
}

func TestAutomationRunnerLogBlobStorageRejectsOverlongStreamBeforeBlobWrite(t *testing.T) {
	ctx := context.Background()
	svc, store, blobStore, now := newAutomationBlobTestService(t)
	countingStore := &countingAutomationBlobStore{Store: blobStore}
	svc.SetBlobStore(countingStore)
	session := registerAutomationBlobTestRunner(t, ctx, svc, "ws-1", "runner-1", "session-1")
	job := createAutomationBlobTestJob(t, ctx, store, "ws-1", "run-1", "job-1", session.LeaseOwner, now())

	_, err := svc.AppendAutomationRunnerJobLog(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, LogStream: strings.Repeat("s", 33), LogContent: "late", LogSequence: 1})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "stream")
	require.Zero(t, countingStore.puts)
}

func TestAutomationRunnerLogBlobStorageDeletesObjectWhenLeaseExpiresAfterBlobWrite(t *testing.T) {
	ctx := context.Background()
	svc, store, blobStore, now := newAutomationBlobTestService(t)
	session := registerAutomationBlobTestRunner(t, ctx, svc, "ws-1", "runner-1", "session-1")
	job := createAutomationBlobTestJob(t, ctx, store, "ws-1", "run-1", "job-1", session.LeaseOwner, now())
	objectKey, err := automationLogChunkObjectKey("ws-1", "run-1", "job-1", "stdout", 1)
	require.NoError(t, err)
	svc.SetBlobStore(leaseExpiringBlobStore{
		Store: blobStore,
		expire: func(ctx context.Context) error {
			_, err := store.RenewLeasedJob(ctx, "ws-1", job.ID, session.LeaseOwner, now(), now().Add(-time.Second))
			return err
		},
	})

	_, err = svc.AppendAutomationRunnerJobLog(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, LogStream: "stdout", LogContent: "late", LogSequence: 1})
	require.ErrorIs(t, err, ErrNotFound)
	_, err = blobStore.Get(ctx, objectKey)
	require.ErrorIs(t, err, blob.ErrNotFound)
}

func TestAutomationRunnerArtifactBlobStorageDeletesObjectWhenLeaseExpiresAfterBlobWrite(t *testing.T) {
	ctx := context.Background()
	svc, store, blobStore, now := newAutomationBlobTestService(t)
	session := registerAutomationBlobTestRunner(t, ctx, svc, "ws-1", "runner-1", "session-1")
	job := createAutomationBlobTestJob(t, ctx, store, "ws-1", "run-1", "job-1", session.LeaseOwner, now())
	payload := []byte("artifact")
	countingStore := &countingAutomationBlobStore{Store: blobStore}
	svc.SetBlobStore(leaseExpiringBlobStore{
		Store: countingStore,
		expire: func(ctx context.Context) error {
			_, err := store.RenewLeasedJob(ctx, "ws-1", job.ID, session.LeaseOwner, now(), now().Add(-time.Second))
			return err
		},
	})

	_, err := svc.AppendAutomationRunnerJobArtifact(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{
		RunnerID:              "runner-1",
		SessionID:             "session-1",
		QueueJobID:            job.ID,
		ArtifactName:          "build.txt",
		ArtifactSize:          int64(len(payload)),
		ArtifactContentBase64: base64.StdEncoding.EncodeToString(payload),
		ArtifactSHA256:        blob.SHA256Hex(payload),
		ArtifactContentType:   "text/plain",
	})

	require.ErrorIs(t, err, ErrNotFound)
	require.NotEmpty(t, countingStore.lastPutKey)
	_, err = blobStore.Get(ctx, countingStore.lastPutKey)
	require.ErrorIs(t, err, blob.ErrNotFound)
	artifacts, err := store.ListArtifacts(ctx, "ws-1", "run-1", "job-1", 1, 30)
	require.NoError(t, err)
	require.Empty(t, artifacts)
}

func TestAutomationRunnerArtifactBlobStorageValidatesHashAndScopesMetadata(t *testing.T) {
	ctx := context.Background()
	svc, store, _, now := newAutomationBlobTestService(t)
	session := registerAutomationBlobTestRunner(t, ctx, svc, "ws-1", "runner-1", "session-1")
	job := createAutomationBlobTestJob(t, ctx, store, "ws-1", "run-1", "job-1", session.LeaseOwner, now())
	payload := []byte("artifact")
	encoded := base64.StdEncoding.EncodeToString(payload)
	sha := blob.SHA256Hex(payload)

	_, err := svc.AppendAutomationRunnerJobArtifact(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, ArtifactName: "build.txt", ArtifactSize: int64(len(payload)), ArtifactContentBase64: encoded, ArtifactSHA256: strings.Repeat("0", 64), ArtifactContentType: "text/plain"})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "sha256")

	oversizedEncoded := base64.StdEncoding.EncodeToString([]byte(strings.Repeat("x", 64)))
	_, err = svc.AppendAutomationRunnerJobArtifact(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, ArtifactName: "oversized.txt", ArtifactSize: 1, ArtifactContentBase64: oversizedEncoded, ArtifactSHA256: sha, ArtifactContentType: "text/plain"})
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Contains(t, err.Error(), "size does not match content")

	event, err := svc.AppendAutomationRunnerJobArtifact(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, ArtifactName: "build.txt", ArtifactSize: int64(len(payload)), ArtifactContentBase64: encoded, ArtifactSHA256: sha, ArtifactContentType: "text/plain"})
	require.NoError(t, err)
	require.Contains(t, event.MetadataJSON, `"object_key"`)
	require.Contains(t, event.MetadataJSON, sha)
	require.NotContains(t, event.MetadataJSON, encoded)

	artifacts, err := svc.ListAutomationArtifacts(ctx, "ws-1", "user-1", "run-1", "", 1, 30)
	require.NoError(t, err)
	require.Len(t, artifacts, 1)
	content, err := svc.ReadAutomationArtifactContent(ctx, "ws-1", "user-1", "run-1", artifacts[0].ID)
	require.NoError(t, err)
	require.Equal(t, payload, content.Content)

	_, err = store.GetArtifact(ctx, "ws-2", "run-1", artifacts[0].ID)
	require.True(t, errors.Is(err, gorm.ErrRecordNotFound))
	_, err = svc.ReadAutomationArtifactContent(ctx, "ws-2", "user-1", "run-1", artifacts[0].ID)
	require.ErrorIs(t, err, ErrNotFound)
}

func TestAutomationArtifactContentAuthorizesConcreteResourceBeforeBlobRead(t *testing.T) {
	ctx := context.Background()
	svc, store, blobStore, now := newAutomationBlobTestService(t)
	countingStore := &countingAutomationBlobStore{Store: blobStore}
	svc.SetBlobStore(countingStore)
	session := registerAutomationBlobTestRunner(t, ctx, svc, "ws-1", "runner-1", "session-1")
	job := createAutomationBlobTestJob(t, ctx, store, "ws-1", "run-1", "job-1", session.LeaseOwner, now())
	payload := []byte("artifact")
	encoded := base64.StdEncoding.EncodeToString(payload)

	_, err := svc.AppendAutomationRunnerJobArtifact(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, ArtifactName: "build.txt", ArtifactSize: int64(len(payload)), ArtifactContentBase64: encoded, ArtifactSHA256: blob.SHA256Hex(payload), ArtifactContentType: "text/plain"})
	require.NoError(t, err)
	artifacts, err := store.ListArtifacts(ctx, "ws-1", "run-1", "job-1", 1, 30)
	require.NoError(t, err)
	require.Len(t, artifacts, 1)
	countingStore.gets = 0

	authorizer := &automationBlobContentReadAuthorizer{
		deny: func(request AutomationAuthorizationRequest) bool {
			return request.Run.ID == "run-1" && request.ArtifactID == artifacts[0].ID
		},
	}
	svc.SetAuthorizer(authorizer)

	_, err = svc.ReadAutomationArtifactContent(ctx, "ws-1", "user-1", "run-1", artifacts[0].ID)
	require.ErrorIs(t, err, ErrForbidden)
	require.Zero(t, countingStore.gets)
	require.True(t, authorizer.saw(func(request AutomationAuthorizationRequest) bool {
		return request.Run.ID == "run-1" && request.ArtifactID == artifacts[0].ID
	}), "CanReadRun did not receive the concrete artifact content scope")
}

func TestAutomationBlobLifecycleSweepsExpiredArtifactsOrphansAndReportsMissingObjects(t *testing.T) {
	ctx := context.Background()
	svc, store, blobStore, now := newAutomationBlobTestService(t)
	session := registerAutomationBlobTestRunner(t, ctx, svc, "ws-1", "runner-1", "session-1")
	job := createAutomationBlobTestJob(t, ctx, store, "ws-1", "run-1", "job-1", session.LeaseOwner, now())
	payload := []byte("artifact")
	encoded := base64.StdEncoding.EncodeToString(payload)
	expiresAt := now().Add(-time.Minute).Format(time.RFC3339Nano)
	_, err := svc.AppendAutomationRunnerJobArtifact(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, ArtifactName: "expired.txt", ArtifactSize: int64(len(payload)), ArtifactContentBase64: encoded, ArtifactSHA256: blob.SHA256Hex(payload), ArtifactContentType: "text/plain", ArtifactExpiresAt: expiresAt})
	require.NoError(t, err)
	artifacts, err := store.ListArtifacts(ctx, "ws-1", "run-1", "job-1", 1, 30)
	require.NoError(t, err)
	require.Len(t, artifacts, 1)
	artifactKey := artifacts[0].ObjectKey

	result, err := svc.SweepExpiredAutomationArtifacts(ctx, 10)
	require.NoError(t, err)
	require.EqualValues(t, 1, result.ExpiredArtifactsScanned)
	require.EqualValues(t, 1, result.ExpiredArtifactsDeleted)
	_, err = blobStore.Get(ctx, artifactKey)
	require.ErrorIs(t, err, blob.ErrNotFound)
	artifacts, err = store.ListArtifacts(ctx, "ws-1", "run-1", "job-1", 1, 30)
	require.NoError(t, err)
	require.Empty(t, artifacts)

	recentKey := "workspaces/ws-1/runs/run-1/jobs/job-1/artifacts/orphan/recent.txt"
	require.NoError(t, blobStore.Put(ctx, recentKey, strings.NewReader("recent"), int64(len("recent"))))
	svc.SetBlobStore(automationBlobTestLister{Store: blobStore, objects: []blob.Object{{Key: recentKey, ModTime: now()}}})
	result, err = svc.SweepAutomationOrphanBlobs(ctx, "workspaces", 10)
	require.NoError(t, err)
	require.EqualValues(t, 1, result.OrphanObjectsScanned)
	require.Zero(t, result.OrphanObjectsDeleted)
	recent, err := blobStore.Get(ctx, recentKey)
	require.NoError(t, err)
	require.NoError(t, recent.Close())

	orphanKey := "workspaces/ws-1/runs/run-1/jobs/job-1/artifacts/orphan/orphan.txt"
	require.NoError(t, blobStore.Put(ctx, orphanKey, strings.NewReader("orphan"), int64(len("orphan"))))
	svc.SetBlobStore(automationBlobTestLister{Store: blobStore, objects: []blob.Object{{Key: orphanKey, ModTime: now().Add(-time.Hour)}}})
	result, err = svc.SweepAutomationOrphanBlobs(ctx, "workspaces", 10)
	require.NoError(t, err)
	require.EqualValues(t, 1, result.OrphanObjectsScanned)
	require.EqualValues(t, 1, result.OrphanObjectsDeleted)
	_, err = blobStore.Get(ctx, orphanKey)
	require.ErrorIs(t, err, blob.ErrNotFound)
	svc.SetBlobStore(blobStore)

	_, err = svc.AppendAutomationRunnerJobLog(ctx, "ws-1", session.SessionToken, AutomationRunnerJobMutation{RunnerID: "runner-1", SessionID: "session-1", QueueJobID: job.ID, LogStream: "stdout", LogContent: "log", LogSequence: 1})
	require.NoError(t, err)
	chunks, err := store.ListLogChunks(ctx, "ws-1", "run-1", "job-1", "stdout", 1, 30)
	require.NoError(t, err)
	require.Len(t, chunks, 1)
	require.NoError(t, blobStore.Delete(ctx, chunks[0].ObjectKey))

	health, err := svc.CheckAutomationBlobObjects(ctx, 10)
	require.NoError(t, err)
	require.EqualValues(t, 1, health.Checked)
	require.Len(t, health.Missing, 1)
	require.Equal(t, "log", health.Missing[0].Kind)
	require.Equal(t, chunks[0].ObjectKey, health.Missing[0].ObjectKey)
}

func newAutomationBlobTestService(t *testing.T) (*AutomationsService, *automationstore.Store, blob.Store, func() time.Time) {
	t.Helper()
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db, &automationstore.AutomationRunner{}, &automationstore.AutomationRunnerToken{}, &automationstore.AutomationRunnerSession{}, &automationstore.AutomationActionsRunnerCredential{}, &automationstore.AutomationLogChunk{}, &automationstore.AutomationArtifact{})
	store := automationstore.New(db)
	current := time.Date(2026, 6, 5, 12, 30, 0, 0, time.UTC)
	blobStore, err := blob.NewFilesystem(t.TempDir())
	require.NoError(t, err)
	svc := NewAutomationsService(store, func() time.Time { return current }, nil)
	svc.SetBlobStore(blobStore)
	return svc, store, blobStore, func() time.Time { return current }
}

func registerAutomationBlobTestRunner(t *testing.T, ctx context.Context, svc *AutomationsService, workspaceID string, runnerID string, sessionID string) AutomationRunnerSession {
	t.Helper()
	registration, err := svc.RegisterAutomationRunner(ctx, workspaceID, "user-1", AutomationRunnerRegisterMutation{RunnerID: runnerID, Name: runnerID, Labels: []string{"self-hosted"}})
	require.NoError(t, err)
	session, err := svc.CreateAutomationRunnerSession(ctx, workspaceID, registration.RegistrationToken, AutomationRunnerSessionMutation{RunnerID: runnerID, SessionID: sessionID})
	require.NoError(t, err)
	return session
}

func createAutomationBlobTestJob(t *testing.T, ctx context.Context, store *automationstore.Store, workspaceID string, runID string, jobRunID string, leaseOwner string, now time.Time) automationstore.AutomationJob {
	t.Helper()
	return createAutomationBlobTestJobWithLeaseUntil(t, ctx, store, workspaceID, runID, jobRunID, leaseOwner, now, now.Add(time.Hour))
}

func createAutomationBlobTestJobWithLeaseUntil(t *testing.T, ctx context.Context, store *automationstore.Store, workspaceID string, runID string, jobRunID string, leaseOwner string, now time.Time, leaseUntil time.Time) automationstore.AutomationJob {
	t.Helper()
	_, err := store.CreateRun(ctx, automationstore.AutomationRun{ID: runID, WorkspaceID: workspaceID, Name: "Blob", Status: automationstore.AutomationRunStatusRunning, CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)
	require.NoError(t, store.CreateJobRuns(ctx, []automationstore.AutomationJobRun{{ID: jobRunID, WorkspaceID: workspaceID, RunID: runID, JobID: "build", JobType: "github_actions_job", Status: automationstore.AutomationJobRunStatusRunning, ScheduledAt: now, CreatedAt: now, UpdatedAt: now}}))
	job, err := store.EnqueueJob(ctx, automationstore.AutomationJob{WorkspaceID: workspaceID, RunID: runID, JobRunID: jobRunID, QueueName: "default", Status: automationstore.AutomationJobStatusLeased, LeaseOwner: leaseOwner, LeaseUntil: &leaseUntil, ScheduledAt: now, MaxAttempts: 3, IdempotencyKey: runID + ":build", ShardBucket: 0, CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)
	return job
}

type leaseExpiringBlobStore struct {
	blob.Store
	expire func(context.Context) error
}

func (store leaseExpiringBlobStore) Put(ctx context.Context, key string, r io.Reader, size int64) error {
	if err := store.Store.Put(ctx, key, r, size); err != nil {
		return err
	}
	return store.expire(ctx)
}

type countingAutomationBlobStore struct {
	blob.Store
	puts       int
	gets       int
	lastPutKey string
}

func (store *countingAutomationBlobStore) Put(ctx context.Context, key string, r io.Reader, size int64) error {
	store.puts++
	store.lastPutKey = key
	return store.Store.Put(ctx, key, r, size)
}

func (store *countingAutomationBlobStore) Get(ctx context.Context, key string) (io.ReadCloser, error) {
	store.gets++
	return store.Store.Get(ctx, key)
}

type automationBlobTestLister struct {
	blob.Store
	objects []blob.Object
}

func (store automationBlobTestLister) List(context.Context, string, int) ([]blob.Object, error) {
	return append([]blob.Object(nil), store.objects...), nil
}

type automationBlobContentReadAuthorizer struct {
	AllowAutomationAuthorizer
	deny     func(AutomationAuthorizationRequest) bool
	requests []AutomationAuthorizationRequest
}

func (authorizer *automationBlobContentReadAuthorizer) CanReadRun(_ context.Context, request AutomationAuthorizationRequest) error {
	authorizer.requests = append(authorizer.requests, request)
	if authorizer.deny != nil && authorizer.deny(request) {
		return forbidden("Automation content read denied")
	}
	return nil
}

func (authorizer *automationBlobContentReadAuthorizer) saw(match func(AutomationAuthorizationRequest) bool) bool {
	for _, request := range authorizer.requests {
		if match(request) {
			return true
		}
	}
	return false
}
