package execution

import (
	"bytes"
	"context"
	"crypto/sha256"
	"database/sql"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/access"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/approval"
	executionstore "github.com/tidbcloud/AgentTeam/internal/store/role/execution"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

const (
	SubjectType          = "role_management"
	RequestType          = "role_management"
	PayloadSchemaVersion = 1
)

var (
	ErrNotImplemented = errors.New("role management service is not implemented")
	ErrInvariant      = errors.New("role management invariant violated")
	ErrConflict       = errors.New("role management request conflicts with an existing Approval")
	ErrForbidden      = errors.New("role management request is forbidden")
)

// ApprovalPayload is the minimal immutable Role request stored by generic
// Approval. Approval Instance fields remain authoritative for applicant member,
// idempotency, digest, decision, decider, correlation, and timestamps.
type ApprovalPayload struct {
	SchemaVersion    int                 `json:"schemaVersion"`
	RequestID        string              `json:"requestId"`
	ApplicantAgentID string              `json:"applicantAgentId"`
	Action           RoleRequestAction   `json:"action"`
	Create           *RoleCreateProposal `json:"create,omitempty"`
	Update           *RoleUpdateProposal `json:"update,omitempty"`
	Delete           *RoleDeleteProposal `json:"delete,omitempty"`
	Apply            *RoleApplyProposal  `json:"apply,omitempty"`
}

// Manager is a mutable Human manager resolution. It is intentionally absent
// from ApprovalPayload and must be re-resolved before submission and execution.
type Manager struct {
	UserID   string
	MemberID string
}

// AgentPrincipal is the transport-neutral identity projection needed by Role
// reads and request status lookup. The service owns conversion to store types.
type AgentPrincipal agent.AuthenticatedAgent

type SubmitCommand struct {
	Principal     agent.AuthenticatedAgent
	RequestID     string
	Proposal      RoleProposal
	CorrelationID string
	Now           time.Time
}

type ApprovalLookup struct {
	WorkspaceID       string
	ApplicantAgentID  string
	ApplicantMemberID string
	IdempotencyKey    string
}

// SubmissionCommit is consumed inside the final Core transaction. The
// implementation must revalidate Principal and require Manager to remain
// current before creating the generic Approval.
type SubmissionCommit struct {
	Principal         agent.AuthenticatedAgent
	Manager           Manager
	Payload           ApprovalPayload
	DeterministicJSON []byte
	PayloadDigest     string
	IdempotencyKey    string
	CorrelationID     string
	Now               time.Time
}

type PreflightCommand struct {
	Principal agent.AuthenticatedAgent
	Manager   Manager
	Payload   ApprovalPayload
}

type RequestGetCommand struct {
	Principal AgentPrincipal
	RequestID string
}

type RequestStatus string

const (
	RequestPending    RequestStatus = "pending"
	RequestProcessing RequestStatus = "processing"
	RequestSucceeded  RequestStatus = "succeeded"
	RequestRejected   RequestStatus = "rejected"
	RequestFailed     RequestStatus = "failed"
	RequestCancelled  RequestStatus = "cancelled"
)

type RequestResult struct {
	ApprovalID       string
	ApplicantAgentID string
	RequestID        string
	Action           RoleRequestAction
	Status           RequestStatus
	RoleID           string
	RoleVersionID    string
	AgentID          string
	FailureCode      string
	FailureMessage   string

	// Created is internal submission metadata for the unregistered Approval
	// adapter. Public handlers must never expose it.
	Created bool `json:"-"`
}

type ApplyCommand struct {
	Approval      approval.Instance
	Payload       ApprovalPayload
	OperationKey  string
	RequestDigest string
	Authority     ExecutionAuthority
}

// RecoverAppliedCommand identifies an immutable MeshX operation receipt before
// current Core authority is rechecked. It never authorizes a fresh mutation.
type RecoverAppliedCommand struct {
	Approval      approval.Instance
	Payload       ApprovalPayload
	OperationKey  string
	RequestDigest string
}

// ExecutionAuthority is the ephemeral result of rechecking current Core
// authority immediately before the MeshX call. It is never persisted.
type ExecutionAuthority struct {
	ManagerUserID string
	OwnedAgentIDs []string
}

// ExecutionAuthorityCommand freezes the execute-time checks that must all use
// current Core facts: the applicant still binds to its active member, its
// current manager is the Approval decider, and an apply target is managed by
// that same Human.
type ExecutionAuthorityCommand struct {
	Approval                approval.Instance
	Payload                 ApprovalPayload
	RequiredManagerMemberID string
	ApplyTargetAgentID      string
}

type ApplyOutcome string

const (
	ApplyInvalidRequest    ApplyOutcome = "invalid_request"
	ApplyAuthorityDenied   ApplyOutcome = "authority_denied"
	ApplyNotFound          ApplyOutcome = "not_found"
	ApplyVersionConflict   ApplyOutcome = "version_conflict"
	ApplyOperationConflict ApplyOutcome = "operation_conflict"
	ApplyTimeout           ApplyOutcome = "timeout"
	ApplyServerError       ApplyOutcome = "server_error"
	ApplyContextCancelled  ApplyOutcome = "context_cancelled"
	ApplyResponseLost      ApplyOutcome = "response_lost"
)

// ApplyError classifies whether an execution attempt has a deterministic
// business result or an uncertain transport result that must remain retryable.
type ApplyError struct {
	Outcome ApplyOutcome
}

func (err *ApplyError) Error() string {
	if err == nil {
		return ""
	}
	return "role management apply: " + string(err.Outcome)
}

// Dependencies expose the reviewed transaction and network boundaries without
// inventing a second durable request ledger.
type Dependencies struct {
	AuthenticateApplicant func(context.Context, agent.AuthenticatedAgent) error
	LookupApproval        func(context.Context, ApprovalLookup) (approval.Instance, bool, error)
	ListAssignments       func(context.Context, string, string) ([]approval.Assignment, error)
	ResolveManager        func(context.Context, AgentPrincipal) (Manager, error)
	Preflight             func(context.Context, PreflightCommand) error
	CommitSubmission      func(context.Context, SubmissionCommit) (approval.Instance, bool, error)

	DecideWithEffect func(context.Context, approval.DecideParams, approval.DecisionEffect) (approval.DecisionResult, error)
	EnqueueInTx      func(context.Context, coredb.Tx, string, string, time.Time) (executionstore.Execution, bool, error)
	LoadExecution    func(context.Context, string, string) (executionstore.Execution, error)

	LoadApproval              func(context.Context, string, string) (approval.Instance, error)
	LoadProjectedTerminal     func(context.Context, approval.Instance, ApprovalPayload) (RequestResult, bool, error)
	RecoverApplied            func(context.Context, RecoverAppliedCommand) (executionstore.Success, bool, error)
	RecheckExecutionAuthority func(context.Context, ExecutionAuthorityCommand) (ExecutionAuthority, error)
	Apply                     func(context.Context, ApplyCommand) (executionstore.Success, error)
	ProjectTerminal           func(context.Context, approval.Instance, RequestResult) error
	Succeed                   func(context.Context, executionstore.ExecutionFence, executionstore.Success, time.Time) error
	ReleaseUncertain          func(context.Context, executionstore.ExecutionFence, time.Time) error
	FailDeterministic         func(context.Context, executionstore.ExecutionFence, executionstore.Failure, time.Time) error
	Now                       func() time.Time
}

// CoreSubmissionStore is the minimal Core DB interface needed to atomically
// perform inner replay, identity/manager fencing, and Approval creation.
type CoreSubmissionStore struct {
	DB        coredb.DB
	Approvals *approval.Store
}

// RecheckExecutionAuthority revalidates current Core ownership immediately
// before an approved Role request reaches MeshX.
func (store CoreSubmissionStore) RecheckExecutionAuthority(
	ctx context.Context,
	command ExecutionAuthorityCommand,
) (ExecutionAuthority, error) {
	if store.DB == nil {
		return ExecutionAuthority{}, errors.New(
			"role management Core submission store is not configured",
		)
	}
	if !validExecutionAuthorityCommand(command) {
		return ExecutionAuthority{}, ErrInvariant
	}
	var managerUserID string
	err := store.DB.QueryRow(ctx, `
SELECT applicant_machine.created_by_user_id
FROM (
  SELECT
    $1 AS workspace_id,
    $2 AS applicant_agent_id,
    $3 AS applicant_member_id,
    $4 AS manager_member_id,
    $5 AS target_agent_id
) authority_input
JOIN agents applicant
  ON applicant.workspace_id=authority_input.workspace_id
 AND applicant.id=authority_input.applicant_agent_id
JOIN members applicant_member
  ON applicant_member.workspace_id=applicant.workspace_id
 AND applicant_member.id=applicant.member_id
JOIN machines applicant_machine
  ON applicant_machine.workspace_id=applicant.workspace_id
 AND applicant_machine.id=applicant.machine_id
JOIN members manager_member
  ON manager_member.workspace_id=applicant.workspace_id
 AND manager_member.id=authority_input.manager_member_id
 AND manager_member.user_id=applicant_machine.created_by_user_id
WHERE applicant.member_id=authority_input.applicant_member_id
  AND applicant.status<>'archived'
  AND applicant_member.type='agent'
  AND applicant_member.status='active'
  AND applicant_machine.status='active'
  AND manager_member.type='human'
  AND manager_member.status='active'
  AND (
    authority_input.target_agent_id='' OR EXISTS (
      SELECT 1
      FROM agents target
      JOIN machines target_machine
        ON target_machine.workspace_id=target.workspace_id
       AND target_machine.id=target.machine_id
      WHERE target.workspace_id=applicant.workspace_id
        AND target.id=authority_input.target_agent_id
        AND target.status<>'archived'
        AND target_machine.status='active'
        AND target_machine.created_by_user_id=applicant_machine.created_by_user_id
    )
  )
LIMIT 1`,
		command.Approval.WorkspaceID,
		command.Payload.ApplicantAgentID,
		command.Approval.ApplicantMemberID,
		command.RequiredManagerMemberID,
		command.ApplyTargetAgentID,
	).Scan(&managerUserID)
	if errors.Is(err, sql.ErrNoRows) {
		return ExecutionAuthority{}, &ApplyError{Outcome: ApplyAuthorityDenied}
	}
	if err != nil {
		return ExecutionAuthority{}, fmt.Errorf(
			"recheck Role execution authority: %w", err,
		)
	}
	authority := ExecutionAuthority{
		ManagerUserID: managerUserID,
		OwnedAgentIDs: []string{},
	}
	if command.ApplyTargetAgentID != "" {
		authority.OwnedAgentIDs = []string{command.ApplyTargetAgentID}
	}
	return authority, nil
}

func validExecutionAuthorityCommand(command ExecutionAuthorityCommand) bool {
	if !isTrimmedString(command.Approval.WorkspaceID) ||
		!isTrimmedString(command.Approval.ApplicantMemberID) ||
		!isTrimmedString(command.Approval.DecidedByMemberID) ||
		!isTrimmedString(command.Payload.ApplicantAgentID) ||
		!isTrimmedString(command.RequiredManagerMemberID) ||
		command.RequiredManagerMemberID != command.Approval.DecidedByMemberID {
		return false
	}
	branchCount := 0
	action := RoleRequestAction("")
	if command.Payload.Create != nil {
		branchCount++
		action = ActionCreate
	}
	if command.Payload.Update != nil {
		branchCount++
		action = ActionUpdate
	}
	if command.Payload.Delete != nil {
		branchCount++
		action = ActionDelete
	}
	if command.Payload.Apply != nil {
		branchCount++
		action = ActionApply
	}
	if branchCount != 1 || action != command.Payload.Action {
		return false
	}
	if command.Payload.Apply == nil {
		return command.ApplyTargetAgentID == ""
	}
	return isTrimmedString(command.ApplyTargetAgentID) &&
		command.ApplyTargetAgentID == command.Payload.Apply.AgentID
}

// AuthenticateApplicant verifies that a trusted principal still names one
// active Agent/member/machine tuple. The final commit repeats this under locks.
func (store CoreSubmissionStore) AuthenticateApplicant(
	ctx context.Context,
	principal agent.AuthenticatedAgent,
) error {
	if store.DB == nil {
		return errors.New("role management Core submission store is not configured")
	}
	var count int
	if err := store.DB.QueryRow(ctx, `
SELECT COUNT(*)
FROM agents a
JOIN members m
  ON m.workspace_id=a.workspace_id AND m.id=a.member_id
JOIN machines mac
  ON mac.workspace_id=a.workspace_id AND mac.id=a.machine_id
WHERE a.workspace_id=$1 AND a.id=$2 AND a.member_id=$3 AND a.machine_id=$4
  AND a.status<>'archived' AND m.type='agent' AND m.status='active'
  AND mac.status='active'`,
		strings.TrimSpace(principal.WorkspaceID),
		strings.TrimSpace(principal.AgentID),
		strings.TrimSpace(principal.MemberID),
		strings.TrimSpace(principal.MachineID),
	).Scan(&count); err != nil {
		return fmt.Errorf("validate Role management applicant: %w", err)
	}
	if count != 1 {
		return ErrForbidden
	}
	return nil
}

// LookupApproval performs the outer read-only replay lookup.
func (store CoreSubmissionStore) LookupApproval(
	ctx context.Context,
	lookup ApprovalLookup,
) (approval.Instance, bool, error) {
	if store.Approvals == nil {
		return approval.Instance{}, false, errors.New("role management Approval store is not configured")
	}
	instance, err := store.Approvals.LoadByIdempotencyKey(
		ctx,
		lookup.WorkspaceID,
		lookup.ApplicantMemberID,
		lookup.IdempotencyKey,
	)
	if errors.Is(err, storeerr.ErrNotFound) {
		return approval.Instance{}, false, nil
	}
	if err != nil {
		return approval.Instance{}, false, err
	}
	return instance, true, nil
}

func (store CoreSubmissionStore) ListAssignments(
	ctx context.Context,
	workspaceID, approvalID string,
) ([]approval.Assignment, error) {
	if store.Approvals == nil {
		return nil, errors.New("role management Approval store is not configured")
	}
	return store.Approvals.ListAssignments(ctx, workspaceID, approvalID)
}

// ResolveManager snapshots the current machine creator and active Human member
// for preflight. CommitSubmission later requires the same pair under locks.
func (store CoreSubmissionStore) ResolveManager(
	ctx context.Context,
	principal AgentPrincipal,
) (Manager, error) {
	if store.DB == nil {
		return Manager{}, errors.New("role management Core submission store is not configured")
	}
	userID, err := access.AgentManagedByUser(
		ctx, store.DB, principal.WorkspaceID, principal.AgentID,
	)
	if err != nil {
		return Manager{}, err
	}
	member, err := access.LoadHumanWorkspaceMember(
		ctx, store.DB, userID, principal.WorkspaceID,
	)
	if err != nil {
		return Manager{}, err
	}
	return Manager{UserID: userID, MemberID: member.Member.ID}, nil
}

// CommitSubmission returns the production Core transaction adapter used by the
// transport-neutral service. It never performs network I/O.
func (store CoreSubmissionStore) CommitSubmission(
	ctx context.Context,
	commit SubmissionCommit,
) (approval.Instance, bool, error) {
	if store.DB == nil || store.Approvals == nil {
		return approval.Instance{}, false, errors.New("role management Core submission store is not configured")
	}
	var result approval.Instance
	var created bool
	err := coredb.WithTx(ctx, store.DB, func(tx coredb.Tx) error {
		txApprovals := store.Approvals.InTx(tx)
		existing, err := txApprovals.LoadByIdempotencyKey(
			ctx,
			commit.Principal.WorkspaceID,
			commit.Principal.MemberID,
			commit.IdempotencyKey,
		)
		if err == nil {
			replayed, replayErr := requireSubmissionReplay(
				ctx, txApprovals, existing, commit,
			)
			if replayErr != nil {
				return replayErr
			}
			result = replayed
			return nil
		}
		if !errors.Is(err, storeerr.ErrNotFound) {
			return err
		}
		if err := access.LockAndRevalidateAgentPrincipalManager(
			ctx,
			tx,
			access.AuthenticatedAgentPrincipal{
				WorkspaceID: commit.Principal.WorkspaceID,
				AgentID:     commit.Principal.AgentID,
				MemberID:    commit.Principal.MemberID,
				MachineID:   commit.Principal.MachineID,
			},
			access.HumanManager{
				UserID: commit.Manager.UserID, MemberID: commit.Manager.MemberID,
			},
		); err != nil {
			return err
		}
		approvalID, err := identifiers.NewEntityID("appr")
		if err != nil {
			return fmt.Errorf("generate Role management Approval id: %w", err)
		}
		instance, _, wasCreated, err := txApprovals.Create(ctx, approval.CreateParams{
			ID:                  approvalID,
			WorkspaceID:         commit.Principal.WorkspaceID,
			SubjectType:         SubjectType,
			SubjectRef:          commit.IdempotencyKey,
			SubjectVersion:      1,
			Payload:             commit.DeterministicJSON,
			PayloadDigest:       commit.PayloadDigest,
			IdempotencyKey:      commit.IdempotencyKey,
			ApplicantMemberID:   commit.Principal.MemberID,
			AllowAgentApplicant: true,
			ApproverMemberIDs:   []string{commit.Manager.MemberID},
			CorrelationID:       commit.CorrelationID,
			Now:                 commit.Now,
		})
		if err != nil {
			return err
		}
		result, created = instance, wasCreated
		return nil
	})
	return result, created, err
}

type Service struct {
	dependencies Dependencies
}

type preparedSubmission struct {
	command           SubmitCommand
	payload           ApprovalPayload
	deterministicJSON []byte
	payloadDigest     string
	idempotencyKey    string
	lookup            ApprovalLookup
}

func NewService(dependencies Dependencies) *Service {
	return &Service{dependencies: dependencies}
}

// LookupReplay performs the read-only half of submission idempotency. It never
// resolves mutable manager facts, calls MeshX, or opens the final Core commit.
func (service *Service) LookupReplay(ctx context.Context, command SubmitCommand) (RequestResult, bool, error) {
	if service == nil || service.dependencies.LookupApproval == nil ||
		service.dependencies.ListAssignments == nil {
		return RequestResult{}, false, errors.New("role management replay lookup is not configured")
	}
	prepared, err := prepareSubmission(command)
	if err != nil {
		return RequestResult{}, false, err
	}
	existing, found, err := service.dependencies.LookupApproval(ctx, prepared.lookup)
	if err != nil || !found {
		return RequestResult{}, found, err
	}
	result, err := service.requireReplay(ctx, existing, prepared)
	if err != nil {
		return RequestResult{}, false, err
	}
	return result, true, nil
}

// Submit must authenticate and normalize first, consult Approval for replay
// before mutable manager/MeshX checks, perform MeshX preflight outside Core SQL,
// then recheck identity/manager and create Approval in the final Core tx.
func (service *Service) Submit(ctx context.Context, command SubmitCommand) (RequestResult, error) {
	if service == nil || service.dependencies.AuthenticateApplicant == nil ||
		service.dependencies.LookupApproval == nil || service.dependencies.ListAssignments == nil ||
		service.dependencies.ResolveManager == nil ||
		service.dependencies.Preflight == nil || service.dependencies.CommitSubmission == nil {
		return RequestResult{}, errors.New("role management submit is not configured")
	}
	prepared, err := prepareSubmission(command)
	if err != nil {
		return RequestResult{}, err
	}
	existing, found, err := service.dependencies.LookupApproval(ctx, prepared.lookup)
	if err != nil {
		return RequestResult{}, err
	}
	if found {
		return service.requireReplay(ctx, existing, prepared)
	}
	if err := service.dependencies.AuthenticateApplicant(ctx, prepared.command.Principal); err != nil {
		return RequestResult{}, err
	}
	manager, err := service.dependencies.ResolveManager(
		ctx,
		AgentPrincipal(prepared.command.Principal),
	)
	if err != nil {
		return RequestResult{}, err
	}
	manager.UserID = strings.TrimSpace(manager.UserID)
	manager.MemberID = strings.TrimSpace(manager.MemberID)
	if manager.UserID == "" || manager.MemberID == "" {
		return RequestResult{}, ErrForbidden
	}
	if err := service.dependencies.Preflight(ctx, PreflightCommand{
		Principal: prepared.command.Principal,
		Manager:   manager,
		Payload:   prepared.payload,
	}); err != nil {
		return RequestResult{}, err
	}
	instance, created, err := service.dependencies.CommitSubmission(ctx, SubmissionCommit{
		Principal:         prepared.command.Principal,
		Manager:           manager,
		Payload:           prepared.payload,
		DeterministicJSON: prepared.deterministicJSON,
		PayloadDigest:     prepared.payloadDigest,
		IdempotencyKey:    prepared.idempotencyKey,
		CorrelationID:     prepared.command.CorrelationID,
		Now:               prepared.command.Now,
	})
	if err != nil {
		return RequestResult{}, err
	}
	result, err := service.requireReplay(ctx, instance, prepared)
	result.Created = created
	return result, err
}

// Decide uses Approval.DecideWithEffect. Approve enqueues inside the same Core
// transaction; reject enqueues nothing. A decision replay reads the existing
// execution by Approval ID instead of rerunning the effect.
func (service *Service) Decide(ctx context.Context, params approval.DecideParams) (RequestResult, error) {
	if service == nil || service.dependencies.DecideWithEffect == nil ||
		service.dependencies.EnqueueInTx == nil || service.dependencies.LoadExecution == nil {
		return RequestResult{}, errors.New("role management decide is not configured")
	}
	var decidedExecution *executionstore.Execution
	decision, err := service.dependencies.DecideWithEffect(
		ctx,
		params,
		func(ctx context.Context, tx coredb.Tx, instance approval.Instance, requested string) (approval.DecisionEffectResult, error) {
			payload, err := decodeApprovalPayload(instance)
			if err != nil {
				return approval.DecisionEffectResult{}, err
			}
			if instance.WorkspaceID != strings.TrimSpace(params.WorkspaceID) ||
				payload.ApplicantAgentID == "" {
				return approval.DecisionEffectResult{}, ErrInvariant
			}
			switch requested {
			case approval.DecisionRejected:
				return approval.DecisionEffectResult{}, nil
			case approval.DecisionApproved:
				execution, _, err := service.dependencies.EnqueueInTx(
					ctx, tx, instance.WorkspaceID, instance.ID, params.Now,
				)
				if err != nil {
					return approval.DecisionEffectResult{}, err
				}
				decidedExecution = &execution
				return approval.DecisionEffectResult{}, nil
			default:
				return approval.DecisionEffectResult{}, ErrInvariant
			}
		},
	)
	if err != nil {
		return RequestResult{}, err
	}
	payload, err := decodeApprovalPayload(decision.Instance)
	if err != nil {
		return RequestResult{}, err
	}
	result := baseRequestResult(decision.Instance, payload)
	switch decision.Instance.State {
	case approval.StateRejected:
		result.Status = RequestRejected
		return result, nil
	case approval.StateApproved:
		execution := executionstore.Execution{}
		if decidedExecution != nil {
			execution = *decidedExecution
		} else {
			execution, err = service.dependencies.LoadExecution(
				ctx, decision.Instance.WorkspaceID, decision.Instance.ID,
			)
			if errors.Is(err, executionstore.ErrNotFound) {
				return RequestResult{}, ErrInvariant
			}
			if err != nil {
				return RequestResult{}, err
			}
		}
		return projectExecution(result, execution)
	default:
		return RequestResult{}, ErrInvariant
	}
}

// RequestGet is read-only and same-Agent scoped. It never creates, claims, or
// retries execution work.
func (service *Service) RequestGet(ctx context.Context, command RequestGetCommand) (RequestResult, error) {
	if service == nil || service.dependencies.AuthenticateApplicant == nil ||
		service.dependencies.LookupApproval == nil || service.dependencies.LoadExecution == nil {
		return RequestResult{}, errors.New("role management request-get is not configured")
	}
	command.Principal.WorkspaceID = strings.TrimSpace(command.Principal.WorkspaceID)
	command.Principal.AgentID = strings.TrimSpace(command.Principal.AgentID)
	command.Principal.MemberID = strings.TrimSpace(command.Principal.MemberID)
	command.RequestID = strings.TrimSpace(command.RequestID)
	if command.Principal.WorkspaceID == "" || command.Principal.AgentID == "" ||
		command.Principal.MemberID == "" || command.RequestID == "" {
		return RequestResult{}, ErrForbidden
	}
	if err := service.dependencies.AuthenticateApplicant(
		ctx,
		agent.AuthenticatedAgent(command.Principal),
	); err != nil {
		return RequestResult{}, err
	}
	idempotencyKey, err := RoleIdempotencyKey(
		command.Principal.WorkspaceID,
		command.Principal.AgentID,
		command.RequestID,
	)
	if err != nil {
		return RequestResult{}, err
	}
	instance, found, err := service.dependencies.LookupApproval(ctx, ApprovalLookup{
		WorkspaceID:       command.Principal.WorkspaceID,
		ApplicantAgentID:  command.Principal.AgentID,
		ApplicantMemberID: command.Principal.MemberID,
		IdempotencyKey:    idempotencyKey,
	})
	if err != nil {
		return RequestResult{}, err
	}
	if !found {
		return RequestResult{}, executionstore.ErrNotFound
	}
	payload, err := decodeApprovalPayload(instance)
	if err != nil {
		return RequestResult{}, err
	}
	if instance.WorkspaceID != command.Principal.WorkspaceID ||
		instance.ApplicantMemberID != command.Principal.MemberID ||
		payload.ApplicantAgentID != command.Principal.AgentID {
		return RequestResult{}, ErrForbidden
	}
	if payload.RequestID != command.RequestID {
		return RequestResult{}, ErrInvariant
	}
	result := baseRequestResult(instance, payload)
	switch instance.State {
	case approval.StatePending:
		result.Status = RequestPending
		return result, nil
	case approval.StateRejected:
		result.Status = RequestRejected
		return result, nil
	case approval.StateCancelled:
		result.Status = RequestCancelled
		return result, nil
	case approval.StateApproved:
		execution, err := service.dependencies.LoadExecution(ctx, instance.WorkspaceID, instance.ID)
		if errors.Is(err, executionstore.ErrNotFound) {
			return RequestResult{}, ErrInvariant
		}
		if err != nil {
			return RequestResult{}, err
		}
		switch execution.Status {
		case executionstore.ExecutionQueued, executionstore.ExecutionRunning:
			result.Status = RequestProcessing
		case executionstore.ExecutionSucceeded:
			result.Status = RequestSucceeded
			result.RoleID = execution.ResultRoleID
			result.RoleVersionID = execution.ResultRoleVersionID
		case executionstore.ExecutionFailed:
			result.Status = RequestFailed
			result.FailureCode = execution.FailureCode
			result.FailureMessage = execution.FailureMessage
		default:
			return RequestResult{}, ErrInvariant
		}
		return result, nil
	default:
		return RequestResult{}, ErrInvariant
	}
}

func prepareSubmission(command SubmitCommand) (preparedSubmission, error) {
	command.Principal.WorkspaceID = strings.TrimSpace(command.Principal.WorkspaceID)
	command.Principal.AgentID = strings.TrimSpace(command.Principal.AgentID)
	command.Principal.MemberID = strings.TrimSpace(command.Principal.MemberID)
	command.Principal.MachineID = strings.TrimSpace(command.Principal.MachineID)
	command.RequestID = strings.TrimSpace(command.RequestID)
	command.CorrelationID = strings.TrimSpace(command.CorrelationID)
	if command.Principal.WorkspaceID == "" || command.Principal.AgentID == "" ||
		command.Principal.MemberID == "" || command.Principal.MachineID == "" ||
		command.RequestID == "" {
		return preparedSubmission{}, ErrForbidden
	}
	normalized, err := NormalizeRoleRequest(RoleRequest{
		WorkspaceID:      command.Principal.WorkspaceID,
		ApplicantAgentID: command.Principal.AgentID,
		RequestID:        command.RequestID,
		Proposal:         command.Proposal,
	})
	if err != nil {
		return preparedSubmission{}, err
	}
	command.RequestID = normalized.RequestID
	command.Proposal = normalized.Proposal
	payload := ApprovalPayload{
		SchemaVersion:    PayloadSchemaVersion,
		RequestID:        normalized.RequestID,
		ApplicantAgentID: normalized.ApplicantAgentID,
		Action:           normalized.Proposal.Action(),
	}
	switch proposal := normalized.Proposal.(type) {
	case RoleCreateProposal:
		payload.Create = &proposal
	case RoleUpdateProposal:
		payload.Update = &proposal
	case RoleDeleteProposal:
		payload.Delete = &proposal
	case RoleApplyProposal:
		payload.Apply = &proposal
	default:
		return preparedSubmission{}, ErrInvalidRoleRequest
	}
	raw, err := json.Marshal(payload)
	if err != nil {
		return preparedSubmission{}, fmt.Errorf("marshal Role management Approval payload: %w", err)
	}
	deterministicJSON, err := deterministicJSONBytes(raw)
	if err != nil {
		return preparedSubmission{}, fmt.Errorf("normalize Role management Approval payload: %w", err)
	}
	sum := sha256.Sum256(deterministicJSON)
	idempotencyKey, err := RoleIdempotencyKey(
		normalized.WorkspaceID, normalized.ApplicantAgentID, normalized.RequestID,
	)
	if err != nil {
		return preparedSubmission{}, err
	}
	return preparedSubmission{
		command:           command,
		payload:           payload,
		deterministicJSON: deterministicJSON,
		payloadDigest:     hex.EncodeToString(sum[:]),
		idempotencyKey:    idempotencyKey,
		lookup: ApprovalLookup{
			WorkspaceID:       normalized.WorkspaceID,
			ApplicantAgentID:  normalized.ApplicantAgentID,
			ApplicantMemberID: command.Principal.MemberID,
			IdempotencyKey:    idempotencyKey,
		},
	}, nil
}

func (service *Service) requireReplay(
	ctx context.Context,
	instance approval.Instance,
	prepared preparedSubmission,
) (RequestResult, error) {
	if instance.WorkspaceID != prepared.lookup.WorkspaceID ||
		instance.SubjectType != SubjectType ||
		instance.SubjectRef != prepared.idempotencyKey ||
		instance.SubjectVersion != 1 ||
		instance.ApplicantMemberID != prepared.lookup.ApplicantMemberID ||
		instance.IdempotencyKey != prepared.idempotencyKey {
		return RequestResult{}, ErrConflict
	}
	if service.dependencies.ListAssignments != nil {
		assignments, err := service.dependencies.ListAssignments(
			ctx, instance.WorkspaceID, instance.ID,
		)
		if err != nil {
			return RequestResult{}, err
		}
		if len(assignments) != 1 ||
			assignments[0].ApprovalInstanceID != instance.ID ||
			assignments[0].WorkspaceID != instance.WorkspaceID ||
			strings.TrimSpace(assignments[0].ApproverMemberID) == "" {
			return RequestResult{}, ErrConflict
		}
	}
	payload, err := decodeApprovalPayload(instance)
	if err != nil {
		return RequestResult{}, ErrConflict
	}
	if instance.PayloadDigest != prepared.payloadDigest ||
		!sameDeterministicJSON(instance.Payload, prepared.deterministicJSON) {
		return RequestResult{}, ErrConflict
	}
	result := baseRequestResult(instance, payload)
	switch instance.State {
	case approval.StatePending:
		result.Status = RequestPending
		return result, nil
	case approval.StateRejected:
		result.Status = RequestRejected
		return result, nil
	case approval.StateCancelled:
		result.Status = RequestCancelled
		return result, nil
	case approval.StateApproved:
		if service.dependencies.LoadExecution == nil {
			return RequestResult{}, errors.New("role management replay execution load is not configured")
		}
		execution, err := service.dependencies.LoadExecution(ctx, instance.WorkspaceID, instance.ID)
		if errors.Is(err, executionstore.ErrNotFound) {
			return RequestResult{}, ErrInvariant
		}
		if err != nil {
			return RequestResult{}, err
		}
		return projectExecution(result, execution)
	default:
		return RequestResult{}, ErrInvariant
	}
}

func requireSubmissionReplay(
	ctx context.Context,
	txApprovals *approval.TxStore,
	instance approval.Instance,
	commit SubmissionCommit,
) (approval.Instance, error) {
	if instance.WorkspaceID != strings.TrimSpace(commit.Principal.WorkspaceID) ||
		instance.SubjectType != SubjectType ||
		instance.SubjectRef != strings.TrimSpace(commit.IdempotencyKey) ||
		instance.SubjectVersion != 1 ||
		instance.ApplicantMemberID != strings.TrimSpace(commit.Principal.MemberID) ||
		instance.IdempotencyKey != strings.TrimSpace(commit.IdempotencyKey) ||
		instance.PayloadDigest != strings.TrimSpace(commit.PayloadDigest) ||
		!sameDeterministicJSON(instance.Payload, commit.DeterministicJSON) {
		return approval.Instance{}, ErrConflict
	}
	assignments, err := txApprovals.ListAssignments(
		ctx, instance.WorkspaceID, instance.ID,
	)
	if err != nil {
		return approval.Instance{}, err
	}
	if len(assignments) != 1 ||
		assignments[0].ApprovalInstanceID != instance.ID ||
		assignments[0].WorkspaceID != instance.WorkspaceID ||
		strings.TrimSpace(commit.Manager.MemberID) == "" ||
		assignments[0].ApproverMemberID != commit.Manager.MemberID {
		return approval.Instance{}, ErrConflict
	}
	return instance, nil
}

func baseRequestResult(instance approval.Instance, payload ApprovalPayload) RequestResult {
	result := RequestResult{
		ApprovalID:       instance.ID,
		ApplicantAgentID: payload.ApplicantAgentID,
		RequestID:        payload.RequestID,
		Action:           payload.Action,
	}
	if payload.Action == ActionApply && payload.Apply != nil {
		result.AgentID = payload.Apply.AgentID
	}
	return result
}

func projectExecution(
	result RequestResult,
	execution executionstore.Execution,
) (RequestResult, error) {
	switch execution.Status {
	case executionstore.ExecutionQueued, executionstore.ExecutionRunning:
		result.Status = RequestProcessing
	case executionstore.ExecutionSucceeded:
		result.Status = RequestSucceeded
		result.RoleID = execution.ResultRoleID
		result.RoleVersionID = execution.ResultRoleVersionID
	case executionstore.ExecutionFailed:
		result.Status = RequestFailed
		result.FailureCode = execution.FailureCode
		result.FailureMessage = execution.FailureMessage
	default:
		return RequestResult{}, ErrInvariant
	}
	return result, nil
}

// ValidateApprovalInstance verifies one authoritative Role Approval payload.
// Subject adapters use this without duplicating the digest/schema contract.
func ValidateApprovalInstance(instance approval.Instance) error {
	_, err := decodeApprovalPayload(instance)
	return err
}

func deterministicJSONBytes(raw []byte) ([]byte, error) {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	var value any
	if err := decoder.Decode(&value); err != nil {
		return nil, err
	}
	var trailing any
	if err := decoder.Decode(&trailing); err != io.EOF {
		return nil, fmt.Errorf("trailing JSON content")
	}
	return json.Marshal(value)
}

func sameDeterministicJSON(left, right []byte) bool {
	leftDeterministic, leftErr := deterministicJSONBytes(left)
	rightDeterministic, rightErr := deterministicJSONBytes(right)
	return leftErr == nil && rightErr == nil && bytes.Equal(leftDeterministic, rightDeterministic)
}
