package invocationmanage

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
	"github.com/tidbcloud/AgentTeam/internal/store/workflow/invocation"
	invocationlang "github.com/tidbcloud/AgentTeam/internal/workflow/invocation"
	"github.com/tidbcloud/AgentTeam/internal/workflow/plan"
)

// Repository is the transactional acceptance and immutable read boundary.
// AcceptManual must look up an Actor/idempotency receipt before checking the
// mutable Plan/default role. A new occurrence locks and revalidates the exact
// PlanVersion and current principal, then creates Invocation and Run atomically.
type Repository interface {
	ReplayManual(context.Context, invocation.ReplayManualParams) (invocationlang.AcceptResult, bool, error)
	LoadManualPlanVersion(context.Context, string, string, string) (invocation.ManualPlanVersion, error)
	AcceptManual(context.Context, invocation.AcceptManualParams) (invocationlang.AcceptResult, error)
	LoadInvocation(context.Context, string, string) (invocationlang.Invocation, error)
	LoadRunByInvocation(context.Context, string, string) (invocationlang.Run, error)
	ListInvocations(context.Context, invocation.ListParams) ([]invocation.ListItem, error)
}

type Service struct {
	repository Repository
	clock      Clock
	authority  InputAuthority
}

type Option func(*Service)

func WithClock(clock Clock) Option {
	return func(service *Service) {
		if clock != nil {
			service.clock = clock
		}
	}
}

func WithInputAuthority(authority InputAuthority) Option {
	return func(service *Service) {
		service.authority = authority
	}
}

func New(repository Repository, options ...Option) *Service {
	service := &Service{repository: repository, clock: systemClock{}}
	for _, option := range options {
		if option != nil {
			option(service)
		}
	}
	return service
}

func (service *Service) validate() error {
	if service == nil || service.repository == nil {
		return fmt.Errorf("workflow Invocation repository is not configured")
	}
	if service.clock == nil {
		return fmt.Errorf("workflow Invocation clock is not configured")
	}
	return nil
}

// ReplayManual resolves one already accepted manual occurrence without
// consulting current deployment, owner, membership, role, or Agent-manager
// state. It is a trusted transport preflight only: callers must authenticate
// InvokedBy themselves, and a miss must continue through InvokeManual so the
// complete current authorization and transaction fences still apply.
func (service *Service) ReplayManual(ctx context.Context, input ReplayInput) (invocationlang.AcceptResult, bool, error) {
	if err := service.validate(); err != nil {
		return invocationlang.AcceptResult{}, false, err
	}
	workspaceID, planID, err := requiredScope(input.WorkspaceID, input.PlanID, "Plan")
	if err != nil {
		return invocationlang.AcceptResult{}, false, err
	}
	invokedBy, err := normalizeReplayActor(input.InvokedBy)
	if err != nil {
		return invocationlang.AcceptResult{}, false, err
	}
	idempotencyKey, err := normalizeIdempotencyKey(input.IdempotencyKey)
	if err != nil {
		return invocationlang.AcceptResult{}, false, err
	}
	rawInput := []byte(input.Input)
	if len(bytes.TrimSpace(rawInput)) == 0 {
		rawInput = []byte(`{}`)
	}
	requestInput, err := invocationlang.DeterministicJSONObject("input", rawInput, invocationlang.MaxInputBytes)
	if err != nil {
		return invocationlang.AcceptResult{}, false, inputInvalid("input.invalid_json", "$.input", err.Error())
	}
	teamID := strings.TrimSpace(input.TeamID)
	result, found, err := service.repository.ReplayManual(ctx, invocation.ReplayManualParams{
		WorkspaceID: workspaceID, PlanID: planID, RequestedPlanVersionID: strings.TrimSpace(input.PlanVersionID), RequestInputJSON: requestInput,
		InvokedBy: invokedBy, IdempotencyKey: idempotencyKey, TeamID: teamID,
	})
	if err != nil {
		return invocationlang.AcceptResult{}, found, mapRepositoryError(err)
	}
	if !found {
		return invocationlang.AcceptResult{}, false, nil
	}
	if !result.Replayed {
		return invocationlang.AcceptResult{}, true, fmt.Errorf("replay manual Workflow Invocation: repository returned a non-replay result")
	}
	if err := validateLogicalReceipt(workspaceID, planID, invokedBy, idempotencyKey, result); err != nil {
		return invocationlang.AcceptResult{}, true, fmt.Errorf("replay manual Workflow Invocation: %w", err)
	}
	return result, true, nil
}

// InvokeManual validates caller input against an exact immutable PlanVersion.
// The repository repeats Plan/default and identity checks under lock.
func (service *Service) InvokeManual(ctx context.Context, input ManualInput) (invocationlang.AcceptResult, error) {
	if err := service.validate(); err != nil {
		return invocationlang.AcceptResult{}, err
	}
	workspaceID, planID, err := requiredScope(input.WorkspaceID, input.PlanID, "Plan")
	if err != nil {
		return invocationlang.AcceptResult{}, err
	}
	principal, err := normalizePrincipal(input.Principal)
	if err != nil {
		return invocationlang.AcceptResult{}, err
	}
	invokedBy, err := normalizeActor(input.InvokedBy, principal)
	if err != nil {
		return invocationlang.AcceptResult{}, err
	}
	idempotencyKey, err := normalizeIdempotencyKey(input.IdempotencyKey)
	if err != nil {
		return invocationlang.AcceptResult{}, err
	}
	rawInput := []byte(input.Input)
	if len(bytes.TrimSpace(rawInput)) == 0 {
		rawInput = []byte(`{}`)
	}
	requestInput, err := invocationlang.DeterministicJSONObject("input", rawInput, invocationlang.MaxInputBytes)
	if err != nil {
		return invocationlang.AcceptResult{}, inputInvalid("input.invalid_json", "$.input", err.Error())
	}
	// The explicit Team identity (or none) is the authoritative frozen identity used
	// by both replay preflight and the first Invoke, so an identical retry uses
	// the same Team identity and a changed Team conflicts.
	teamID := strings.TrimSpace(input.TeamID)

	// Replay lookup deliberately precedes every mutable deployment, owner,
	// membership and PlanVersion read. A transport retry must still return its
	// immutable first receipt after Plan archive, default-role, or membership
	// changes. Its digest uses this schema-agnostic caller object, never values
	// after today's Plan defaults have been applied.
	replayed, found, err := service.repository.ReplayManual(ctx, invocation.ReplayManualParams{
		WorkspaceID: workspaceID, PlanID: planID, RequestedPlanVersionID: strings.TrimSpace(input.PlanVersionID), RequestInputJSON: requestInput,
		InvokedBy: invokedBy, IdempotencyKey: idempotencyKey, TeamID: teamID,
	})
	if err != nil {
		return invocationlang.AcceptResult{}, mapRepositoryError(err)
	}
	if found {
		if !replayed.Replayed {
			return invocationlang.AcceptResult{}, fmt.Errorf("replay manual Workflow Invocation: repository returned a non-replay result")
		}
		if err := validateLogicalReceipt(workspaceID, planID, invokedBy, idempotencyKey, replayed); err != nil {
			return invocationlang.AcceptResult{}, fmt.Errorf("replay manual Workflow Invocation: %w", err)
		}
		return replayed, nil
	}

	material, err := service.repository.LoadManualPlanVersion(ctx, workspaceID, planID, strings.TrimSpace(input.PlanVersionID))
	if err != nil {
		return invocationlang.AcceptResult{}, mapRepositoryError(err)
	}
	normalizedInput, err := normalizeManualInput(workspaceID, planID, strings.TrimSpace(input.PlanVersionID), material, requestInput)
	if err != nil {
		return invocationlang.AcceptResult{}, err
	}
	if err := service.authorizeManualInput(ctx, workspaceID, principal, invokedBy, material, normalizedInput); err != nil {
		return invocationlang.AcceptResult{}, err
	}

	result, err := service.repository.AcceptManual(ctx, invocation.AcceptManualParams{
		WorkspaceID: workspaceID, PlanID: planID,
		RequestedPlanVersionID: strings.TrimSpace(input.PlanVersionID),
		ExpectedPlanVersionID:  material.PlanVersionID, ExpectedVersionDigest: material.PlanVersionDigest,
		RequestInputJSON: requestInput,
		InputJSON:        normalizedInput,
		Principal: invocation.Principal{
			UserID: principal.UserID, MemberID: principal.MemberID, Role: principal.Role,
		},
		InvokedBy: invokedBy, IdempotencyKey: idempotencyKey,
		Now:    service.clock.Now().UTC(),
		TeamID: teamID,
	})
	if err != nil {
		return invocationlang.AcceptResult{}, mapRepositoryError(err)
	}
	if err := validateAcceptResult(workspaceID, planID, normalizedInput, invokedBy, idempotencyKey, material, result); err != nil {
		return invocationlang.AcceptResult{}, err
	}
	return result, nil
}

func (service *Service) authorizeManualInput(
	ctx context.Context,
	workspaceID string,
	principal Principal,
	invokedBy invocationlang.Actor,
	material invocation.ManualPlanVersion,
	normalizedInput []byte,
) error {
	if service.authority == nil || invokedBy.Type != invocationlang.ActorTypeAgent {
		return nil
	}
	artifact, err := plan.DecodeArtifact(material.PlanArtifactJSON)
	if err != nil || artifact == nil {
		return planVersionUnavailable("exact PlanVersion artifact is invalid")
	}
	var values map[string]any
	decoder := json.NewDecoder(bytes.NewReader(normalizedInput))
	decoder.UseNumber()
	if err := decoder.Decode(&values); err != nil {
		return fmt.Errorf("decode normalized Workflow Invocation input: %w", err)
	}
	for _, field := range artifact.Contracts.Input {
		value, ok := values[field.Name]
		if !ok {
			continue
		}
		if err := service.authorizeManualInputValue(
			ctx,
			workspaceID,
			principal,
			invokedBy,
			field.Schema,
			value,
			"$.input."+field.Name,
		); err != nil {
			return err
		}
	}
	return nil
}

func (service *Service) authorizeManualInputValue(
	ctx context.Context,
	workspaceID string,
	principal Principal,
	invokedBy invocationlang.Actor,
	schema plan.ArtifactSchema,
	value any,
	path string,
) error {
	switch schema.Type {
	case plan.TypeAgent:
		agentID, _ := value.(string)
		exists, err := service.authority.WorkflowInvocationAgentExists(ctx, workspaceID, agentID)
		if err != nil {
			return err
		}
		if !exists {
			return inputInvalid("input.agent_not_found", path, fmt.Sprintf("Agent %q does not exist in this workspace", agentID))
		}
	case plan.TypeMember:
		memberID, _ := value.(string)
		exists, err := service.authority.WorkflowInvocationMemberExists(ctx, workspaceID, memberID)
		if err != nil {
			return err
		}
		if !exists {
			return inputInvalid("input.member_not_found", path, fmt.Sprintf("Member %q does not exist in this workspace or is not available", memberID))
		}
	case plan.TypeScope:
		scope, err := domain.NormalizeMessageScopeValue(value)
		if err != nil {
			return inputInvalid("input.scope_invalid", path, err.Error())
		}
		if err := service.authority.AuthorizeWorkflowInvocationScope(
			ctx,
			workspaceID,
			invokedBy.ID,
			principal.UserID,
			principal.MemberID,
			scope,
		); err != nil {
			return inputInvalid("input.scope_unavailable", path, fmt.Sprintf(
				"scope %s/%s does not exist in this workspace or is not accessible",
				scope.Kind,
				scope.ScopeID,
			))
		}
	case plan.TypeArray:
		if schema.Items == nil {
			return nil
		}
		items, _ := value.([]any)
		for index, item := range items {
			if err := service.authorizeManualInputValue(
				ctx,
				workspaceID,
				principal,
				invokedBy,
				*schema.Items,
				item,
				fmt.Sprintf("%s[%d]", path, index),
			); err != nil {
				return err
			}
		}
	}
	return nil
}

// normalizeManualInput treats immutable PlanVersion bytes as an untrusted
// store boundary. Corrupt persisted material is operational; caller input
// failures remain stable path-aware validation diagnostics.
func normalizeManualInput(
	workspaceID string,
	planID string,
	requestedPlanVersionID string,
	material invocation.ManualPlanVersion,
	rawInput []byte,
) ([]byte, error) {
	if strings.TrimSpace(material.WorkspaceID) != workspaceID || strings.TrimSpace(material.PlanID) != planID ||
		strings.TrimSpace(material.RequestedPlanVersionID) != requestedPlanVersionID {
		return nil, fmt.Errorf("load direct Workflow PlanVersion: repository returned a different request scope")
	}
	if strings.TrimSpace(material.PlanVersionID) == "" || strings.TrimSpace(material.WorkflowName) == "" {
		return nil, fmt.Errorf("load direct Workflow PlanVersion: repository returned incomplete immutable material")
	}
	if strings.TrimSpace(material.PlanCompilerVersion) == "" || strings.TrimSpace(material.PlanVersionDigest) == "" {
		return nil, planVersionUnavailable("exact PlanVersion metadata is unavailable")
	}
	artifact, err := plan.DecodeArtifact(material.PlanArtifactJSON)
	if err != nil {
		return nil, planVersionUnavailable("exact PlanVersion artifact is invalid")
	}
	if artifact == nil {
		return nil, planVersionUnavailable("exact PlanVersion artifact is unavailable")
	}
	if artifact.CompilerVersion != strings.TrimSpace(material.PlanCompilerVersion) ||
		artifact.Digest != strings.TrimSpace(material.PlanVersionDigest) {
		return nil, planVersionUnavailable("exact PlanVersion artifact metadata does not match")
	}
	normalized, err := plan.NormalizeFinalInputJSON(artifact.Contracts.Input, rawInput)
	if err != nil {
		return nil, mapInputError(err)
	}
	payload, err := normalized.DeterministicJSON()
	if err != nil {
		return nil, fmt.Errorf("encode normalized Workflow Invocation input: %w", err)
	}
	return payload, nil
}

func planVersionUnavailable(message string) error {
	return RequestError{Kind: storeerr.ErrConflict, Code: ErrorCodePlanVersionUnavailable, Message: message}
}

func mapInputError(err error) error {
	var planInput plan.InputError
	if errors.As(err, &planInput) {
		path := "$.input"
		if planInput.Path != "" && planInput.Path != "$" {
			path += strings.TrimPrefix(planInput.Path, "$")
		}
		return inputInvalid(planInput.Code, path, planInput.Message)
	}
	return err
}

func (service *Service) Get(ctx context.Context, workspaceID string, invocationID string) (Record, error) {
	if err := service.validate(); err != nil {
		return Record{}, err
	}
	workspaceID, invocationID, err := requiredScope(workspaceID, invocationID, "Invocation")
	if err != nil {
		return Record{}, err
	}
	value, err := service.repository.LoadInvocation(ctx, workspaceID, invocationID)
	if err != nil {
		return Record{}, mapRepositoryError(err)
	}
	run, err := service.repository.LoadRunByInvocation(ctx, workspaceID, invocationID)
	if err != nil {
		return Record{}, mapRepositoryError(err)
	}
	if err := validateRecord(workspaceID, value, run); err != nil {
		return Record{}, err
	}
	return Record{Invocation: value, Run: run}, nil
}

func (service *Service) List(ctx context.Context, input ListInput) (Page, error) {
	if err := service.validate(); err != nil {
		return Page{}, err
	}
	normalized, err := normalizeList(input)
	if err != nil {
		return Page{}, err
	}
	cursor, err := decodeListCursor(normalized.Cursor)
	if err != nil {
		return Page{}, err
	}
	values, err := service.repository.ListInvocations(ctx, invocation.ListParams{
		WorkspaceID: normalized.WorkspaceID, PlanID: normalized.PlanID,
		Limit: normalized.Limit + 1, BeforeAcceptedAt: cursor.AcceptedAt, BeforeID: cursor.ID,
	})
	if err != nil {
		return Page{}, mapRepositoryError(err)
	}
	hasMore := len(values) > normalized.Limit
	if hasMore {
		values = values[:normalized.Limit]
	}
	page := Page{Items: make([]Summary, 0, len(values))}
	for _, value := range values {
		if err := validateInvocationSummary(normalized.WorkspaceID, value.Invocation, value.RunStatus); err != nil ||
			strings.TrimSpace(value.Invocation.PlanID) != normalized.PlanID {
			return Page{}, fmt.Errorf("list Workflow Invocations: repository returned an invalid or different resource")
		}
		summary := value.Invocation
		summary.InputJSON = nil
		summary.SourceProvenanceJSON = nil
		summary.IdempotencyKey = ""
		summary.RequestDigest = ""
		page.Items = append(page.Items, Summary{Invocation: summary, RunStatus: value.RunStatus})
	}
	if hasMore {
		last := values[len(values)-1].Invocation
		page.NextCursor, err = encodeListCursor(last.AcceptedAt, last.ID)
		if err != nil {
			return Page{}, err
		}
	}
	return page, nil
}

func validateInvocationSummary(workspaceID string, value invocationlang.Invocation, runStatus string) error {
	identity := value.Identity
	if strings.TrimSpace(value.ID) == "" || value.WorkspaceID != workspaceID || strings.TrimSpace(value.RunID) == "" ||
		strings.TrimSpace(value.PlanID) == "" ||
		strings.TrimSpace(value.PlanVersionID) == "" ||
		(value.SourceKind != invocationlang.SourceKindManual && value.SourceKind != invocationlang.SourceKindSchedule) ||
		strings.TrimSpace(value.SourceOccurrenceRef) == "" || value.SourceOccurredAt.IsZero() ||
		len(value.InputDigest) != len("sha256:")+64 || !strings.HasPrefix(value.InputDigest, "sha256:") ||
		strings.TrimSpace(identity.OwnerUserID) == "" || strings.TrimSpace(identity.OwnerMemberID) == "" ||
		strings.TrimSpace(identity.PrincipalMemberID) == "" || strings.TrimSpace(identity.AuthorMemberID) == "" ||
		strings.TrimSpace(runStatus) == "" || value.AcceptedAt.IsZero() {
		return fmt.Errorf("repository returned an invalid Workflow Invocation summary")
	}
	if err := validateSourceSnapshot(value); err != nil {
		return err
	}
	if value.SourceKind == invocationlang.SourceKindManual &&
		(value.InvokedBy.Type != invocationlang.ActorTypeHuman && value.InvokedBy.Type != invocationlang.ActorTypeAgent ||
			strings.TrimSpace(value.InvokedBy.ID) == "" || strings.TrimSpace(value.InvokedBy.MemberID) == "") {
		return fmt.Errorf("repository returned invalid manual Invocation summary provenance")
	}
	return nil
}

func validateAcceptResult(
	workspaceID string,
	planID string,
	inputJSON []byte,
	invokedBy invocationlang.Actor,
	idempotencyKey string,
	material invocation.ManualPlanVersion,
	result invocationlang.AcceptResult,
) error {
	value, run := result.Invocation, result.Run
	if err := validateRecord(workspaceID, value, run); err != nil {
		return fmt.Errorf("accept manual Workflow Invocation: %w", err)
	}
	if err := validateAcceptedEvent(result.Event, value, run); err != nil {
		return fmt.Errorf("accept manual Workflow Invocation: %w", err)
	}
	if value.PlanID != planID || value.SourceKind != invocationlang.SourceKindManual ||
		value.InvokedBy != invokedBy || value.IdempotencyKey != idempotencyKey {
		return fmt.Errorf("accept manual Workflow Invocation: repository returned a different logical receipt")
	}
	if result.Replayed {
		return nil
	}
	if run.Status != invocationlang.RunStatusQueued || !bytes.Equal(value.InputJSON, inputJSON) ||
		value.PlanID != material.PlanID || value.PlanVersionID != material.PlanVersionID {
		return fmt.Errorf("accept manual Workflow Invocation: repository returned a different PlanVersion snapshot")
	}
	return nil
}

func validateLogicalReceipt(
	workspaceID string,
	planID string,
	invokedBy invocationlang.Actor,
	idempotencyKey string,
	result invocationlang.AcceptResult,
) error {
	if err := validateRecord(workspaceID, result.Invocation, result.Run); err != nil {
		return err
	}
	if err := validateAcceptedEvent(result.Event, result.Invocation, result.Run); err != nil {
		return err
	}
	value := result.Invocation
	if value.PlanID != planID || value.SourceKind != invocationlang.SourceKindManual ||
		value.InvokedBy != invokedBy || value.IdempotencyKey != idempotencyKey {
		return fmt.Errorf("repository returned a different logical receipt")
	}
	return nil
}

func validateRecord(workspaceID string, value invocationlang.Invocation, run invocationlang.Run) error {
	if err := validateInvocationSnapshot(workspaceID, value); err != nil {
		return err
	}
	identity := value.Identity
	if run.Identity != identity || strings.TrimSpace(run.ID) == "" || run.ID != value.RunID || run.WorkspaceID != workspaceID || run.InvocationID != value.ID ||
		run.PlanID != value.PlanID ||
		run.PlanVersionID != value.PlanVersionID || strings.TrimSpace(run.Status) == "" || !bytes.Equal(run.InputJSON, value.InputJSON) ||
		run.InputDigest != value.InputDigest || run.InputDigest != invocationlang.InputDigest(run.InputJSON) ||
		run.QueuedAt.IsZero() || run.CreatedAt.IsZero() || run.UpdatedAt.IsZero() {
		return fmt.Errorf("repository returned an inconsistent Workflow Invocation/Run record")
	}
	return nil
}

func validateInvocationSnapshot(workspaceID string, value invocationlang.Invocation) error {
	identity := value.Identity
	input := bytes.TrimSpace(value.InputJSON)
	provenance := bytes.TrimSpace(value.SourceProvenanceJSON)
	if strings.TrimSpace(value.ID) == "" || value.WorkspaceID != workspaceID || strings.TrimSpace(value.RunID) == "" ||
		strings.TrimSpace(value.PlanID) == "" ||
		strings.TrimSpace(value.PlanVersionID) == "" ||
		(value.SourceKind != invocationlang.SourceKindManual && value.SourceKind != invocationlang.SourceKindSchedule) ||
		strings.TrimSpace(value.SourceOccurrenceRef) == "" || value.SourceOccurredAt.IsZero() ||
		len(input) == 0 || input[0] != '{' || !json.Valid(input) || value.InputDigest != invocationlang.InputDigest(input) ||
		len(provenance) == 0 || provenance[0] != '{' || !json.Valid(provenance) ||
		strings.TrimSpace(identity.OwnerUserID) == "" || strings.TrimSpace(identity.OwnerMemberID) == "" ||
		strings.TrimSpace(identity.PrincipalMemberID) == "" || strings.TrimSpace(identity.AuthorMemberID) == "" ||
		strings.TrimSpace(value.IdempotencyKey) == "" || strings.TrimSpace(value.RequestDigest) == "" || value.AcceptedAt.IsZero() {
		return fmt.Errorf("repository returned an invalid Workflow Invocation snapshot")
	}
	if err := validateSourceSnapshot(value); err != nil {
		return err
	}
	if value.SourceKind == invocationlang.SourceKindManual &&
		(value.InvokedBy.Type != invocationlang.ActorTypeHuman && value.InvokedBy.Type != invocationlang.ActorTypeAgent ||
			strings.TrimSpace(value.InvokedBy.ID) == "" || strings.TrimSpace(value.InvokedBy.MemberID) == "") {
		return fmt.Errorf("repository returned invalid manual Invocation provenance")
	}
	return nil
}

func validateSourceSnapshot(value invocationlang.Invocation) error {
	manualBindingEmpty := value.BindingID == "" && value.BindingRevisionID == "" && value.BindingRevision == 0 &&
		value.BindingSourceID == "" && value.BindingActivation == 0
	scheduleBindingComplete := strings.TrimSpace(value.BindingID) != "" && strings.TrimSpace(value.BindingRevisionID) != "" &&
		value.BindingRevision > 0 && strings.TrimSpace(value.BindingSourceID) != "" && value.BindingActivation > 0
	switch value.SourceKind {
	case invocationlang.SourceKindManual:
		if !manualBindingEmpty {
			return fmt.Errorf("repository returned a manual Workflow Invocation with Binding provenance")
		}
	case invocationlang.SourceKindSchedule:
		if !scheduleBindingComplete || value.InvokedBy != (invocationlang.Actor{}) {
			return fmt.Errorf("repository returned an invalid scheduled Workflow Invocation source snapshot")
		}
	default:
		return fmt.Errorf("repository returned an unsupported Workflow Invocation source kind")
	}
	return nil
}

func validateAcceptedEvent(event invocationlang.RunEvent, value invocationlang.Invocation, run invocationlang.Run) error {
	payload := bytes.TrimSpace(event.PayloadJSON)
	if strings.TrimSpace(event.ID) == "" || event.WorkspaceID != value.WorkspaceID || event.RunID != run.ID ||
		event.Seq != 1 || event.Type != invocationlang.RunEventAccepted || event.CreatedAt.IsZero() ||
		!event.CreatedAt.Equal(run.QueuedAt) ||
		len(payload) == 0 || payload[0] != '{' || !json.Valid(payload) {
		return fmt.Errorf("repository returned an invalid accepted Workflow Run event")
	}
	var decoded struct {
		InvocationID      string `json:"invocationId"`
		BindingRevisionID string `json:"bindingRevisionId"`
		PlanVersionID     string `json:"planVersionId"`
		Status            string `json:"status"`
		InputDigest       string `json:"inputDigest"`
	}
	if err := json.Unmarshal(payload, &decoded); err != nil || decoded.InvocationID != value.ID ||
		decoded.BindingRevisionID != value.BindingRevisionID || decoded.PlanVersionID != value.PlanVersionID ||
		decoded.Status != invocationlang.RunStatusQueued || decoded.InputDigest != value.InputDigest {
		return fmt.Errorf("repository returned an inconsistent accepted Workflow Run event")
	}
	return nil
}

func mapRepositoryError(err error) error {
	if err == nil {
		return nil
	}
	var requestError RequestError
	if errors.As(err, &requestError) {
		return err
	}
	var idempotency invocation.IdempotencyConflictError
	if errors.As(err, &idempotency) {
		return RequestError{Kind: storeerr.ErrConflict, Code: ErrorCodeIdempotencyConflict, Message: "The same idempotency key was already used for a different request", Cause: idempotency}
	}
	switch {
	case errors.Is(err, invocation.ErrInactiveDeployment):
		return RequestError{Kind: storeerr.ErrConflict, Code: ErrorCodePlanVersionUnavailable, Message: "Plan is archived or unavailable", Cause: err}
	case errors.Is(err, invocation.ErrPlanVersionUnavailable):
		return RequestError{Kind: storeerr.ErrConflict, Code: ErrorCodePlanVersionUnavailable, Message: "exact PlanVersion is unavailable", Cause: err}
	case errors.Is(err, invocation.ErrPrincipalUnavailable):
		return RequestError{Kind: storeerr.ErrForbidden, Code: ErrorCodePrincipalUnavailable, Message: "Invocation principal is unavailable", Cause: err}
	case errors.Is(err, invocation.ErrDeploymentChanged):
		return RequestError{Kind: storeerr.ErrConflict, Code: ErrorCodePlanVersionUnavailable, Message: "Plan default or exact PlanVersion changed while the Invocation was being accepted", Cause: err}
	case errors.Is(err, storeerr.ErrNotFound):
		return RequestError{Kind: storeerr.ErrNotFound, Code: ErrorCodeNotFound, Message: "Invocation or Plan resource not found", Cause: err}
	case errors.Is(err, storeerr.ErrBadRequest):
		return RequestError{Kind: storeerr.ErrBadRequest, Code: ErrorCodeInvalidRequest, Message: "Invocation request is invalid", Cause: err}
	case errors.Is(err, storeerr.ErrForbidden), errors.Is(err, storeerr.ErrUnauthorized):
		return RequestError{Kind: storeerr.ErrForbidden, Code: ErrorCodeForbidden, Message: "Invocation is forbidden", Cause: err}
	case errors.Is(err, storeerr.ErrConflict):
		return RequestError{Kind: storeerr.ErrConflict, Code: ErrorCodeConflict, Message: "Invocation mutation conflicts with the current state", Cause: err}
	default:
		return err
	}
}
