package execution

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

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

func requireRoleServiceImplemented(t *testing.T, err error) {
	t.Helper()
	if errors.Is(err, ErrNotImplemented) {
		t.Fatalf("RED: %v", err)
	}
	require.NoError(t, err)
}

type recordingDependencies struct {
	calls []string
}

func (deps *recordingDependencies) record(call string) { deps.calls = append(deps.calls, call) }

type sentinelTx struct{ coredb.Tx }

func redPrincipal() agent.AuthenticatedAgent {
	return agent.AuthenticatedAgent{
		WorkspaceID: "ws_1", AgentID: "agt_1", MemberID: "mem_1", MachineID: "mac_1",
	}
}

func redSubmit() SubmitCommand {
	return SubmitCommand{
		Principal: redPrincipal(),
		RequestID: "req_1",
		Proposal:  RoleCreateProposal{Name: "Release", Definition: "Review releases"},
		Now:       time.Unix(100, 0).UTC(),
	}
}

func redApprovalForSubmit(t *testing.T, command SubmitCommand, state string) approval.Instance {
	t.Helper()
	payload := ApprovalPayload{
		SchemaVersion:    PayloadSchemaVersion,
		RequestID:        command.RequestID,
		ApplicantAgentID: command.Principal.AgentID,
	}
	switch proposal := command.Proposal.(type) {
	case RoleCreateProposal:
		payload.Action, payload.Create = ActionCreate, &proposal
	case RoleUpdateProposal:
		payload.Action, payload.Update = ActionUpdate, &proposal
	case RoleDeleteProposal:
		payload.Action, payload.Delete = ActionDelete, &proposal
	case RoleApplyProposal:
		payload.Action, payload.Apply = ActionApply, &proposal
	default:
		t.Fatalf("unsupported RED proposal %T", command.Proposal)
	}
	raw, err := json.Marshal(payload)
	require.NoError(t, err)
	var resolved any
	decoder := json.NewDecoder(strings.NewReader(string(raw)))
	decoder.UseNumber()
	require.NoError(t, decoder.Decode(&resolved))
	raw, err = json.Marshal(resolved)
	require.NoError(t, err)
	sum := sha256.Sum256(raw)
	idempotencyKey, err := RoleIdempotencyKey(
		command.Principal.WorkspaceID, command.Principal.AgentID, command.RequestID,
	)
	require.NoError(t, err)
	return approval.Instance{
		ID: "appr_1", WorkspaceID: "ws_1", SubjectType: SubjectType,
		SubjectRef: idempotencyKey, SubjectVersion: 1,
		Payload: raw, PayloadDigest: hex.EncodeToString(sum[:]),
		IdempotencyKey: idempotencyKey, ApplicantMemberID: "mem_1",
		State: state, DecidedByMemberID: "mem_manager",
	}
}

func redApproval(t *testing.T, state string) approval.Instance {
	t.Helper()
	return redApprovalForSubmit(t, redSubmit(), state)
}

func newREDService(t *testing.T, deps *recordingDependencies) *Service {
	t.Helper()
	instance := redApproval(t, approval.StatePending)
	return NewService(Dependencies{
		AuthenticateApplicant: func(context.Context, agent.AuthenticatedAgent) error {
			deps.record("authenticate")
			return nil
		},
		LookupApproval: func(context.Context, ApprovalLookup) (approval.Instance, bool, error) {
			deps.record("approval-replay-lookup")
			return approval.Instance{}, false, nil
		},
		ListAssignments: func(context.Context, string, string) ([]approval.Assignment, error) {
			return []approval.Assignment{{
				ID: "asg_1", WorkspaceID: "ws_1",
				ApprovalInstanceID: "appr_1", ApproverMemberID: "mem_manager",
			}}, nil
		},
		ResolveManager: func(context.Context, AgentPrincipal) (Manager, error) {
			deps.record("resolve-manager")
			return Manager{UserID: "usr_manager", MemberID: "mem_manager"}, nil
		},
		Preflight: func(context.Context, PreflightCommand) error {
			deps.record("meshx-preflight")
			return nil
		},
		CommitSubmission: func(_ context.Context, commit SubmissionCommit) (approval.Instance, bool, error) {
			deps.record("core-commit-final-auth-manager-recheck-and-create")
			instance.Payload = commit.DeterministicJSON
			instance.PayloadDigest = commit.PayloadDigest
			instance.IdempotencyKey = commit.IdempotencyKey
			return instance, true, nil
		},
		EnqueueInTx: func(context.Context, coredb.Tx, string, string, time.Time) (executionstore.Execution, bool, error) {
			return executionstore.Execution{}, false, nil
		},
		LoadExecution: func(context.Context, string, string) (executionstore.Execution, error) {
			deps.record("load-execution")
			return executionstore.Execution{
				ApprovalID: "appr_1", WorkspaceID: "ws_1", Status: executionstore.ExecutionQueued,
			}, nil
		},
		LoadApproval: func(context.Context, string, string) (approval.Instance, error) {
			deps.record("load-approval")
			return redApproval(t, approval.StateApproved), nil
		},
		RecheckExecutionAuthority: func(context.Context, ExecutionAuthorityCommand) (ExecutionAuthority, error) {
			deps.record("worker-auth-manager-target-recheck")
			return ExecutionAuthority{ManagerUserID: "usr_manager", OwnedAgentIDs: []string{"agt_target"}}, nil
		},
		Apply: func(_ context.Context, command ApplyCommand) (executionstore.Success, error) {
			deps.record("meshx-apply:" + command.OperationKey + ":" + command.RequestDigest)
			return executionstore.Success{RoleID: "role_1", RoleVersionID: "rlv_1"}, nil
		},
		Succeed: func(context.Context, executionstore.ExecutionFence, executionstore.Success, time.Time) error {
			deps.record("succeed")
			return nil
		},
		ReleaseUncertain: func(context.Context, executionstore.ExecutionFence, time.Time) error {
			deps.record("release-uncertain")
			return nil
		},
		FailDeterministic: func(context.Context, executionstore.ExecutionFence, executionstore.Failure, time.Time) error {
			deps.record("fail-deterministic")
			return nil
		},
		Now: func() time.Time { return time.Unix(101, 0).UTC() },
	})
}

func forbidRequestGetMutations(t *testing.T, service *Service) {
	t.Helper()
	service.dependencies.EnqueueInTx = func(context.Context, coredb.Tx, string, string, time.Time) (executionstore.Execution, bool, error) {
		t.Fatal("request-get is read-only and must not enqueue work")
		return executionstore.Execution{}, false, nil
	}
	service.dependencies.Apply = func(context.Context, ApplyCommand) (executionstore.Success, error) {
		t.Fatal("request-get is read-only and must not call MeshX")
		return executionstore.Success{}, nil
	}
	service.dependencies.Succeed = func(context.Context, executionstore.ExecutionFence, executionstore.Success, time.Time) error {
		t.Fatal("request-get is read-only and must not settle work")
		return nil
	}
	service.dependencies.ReleaseUncertain = func(context.Context, executionstore.ExecutionFence, time.Time) error {
		t.Fatal("request-get is read-only and must not release or retry work")
		return nil
	}
	service.dependencies.FailDeterministic = func(context.Context, executionstore.ExecutionFence, executionstore.Failure, time.Time) error {
		t.Fatal("request-get is read-only and must not fail work")
		return nil
	}
}

func TestApprovalPayloadIsMinimalAndExecutionCannotCompeteWithItsDigest(t *testing.T) {
	payloadType := reflect.TypeOf(ApprovalPayload{})
	for _, required := range []string{
		"SchemaVersion", "RequestID", "ApplicantAgentID", "Action",
		"Create", "Update", "Delete", "Apply",
	} {
		_, ok := payloadType.FieldByName(required)
		require.Truef(t, ok, "Approval payload must retain %s", required)
	}
	for _, forbidden := range []string{
		"WorkspaceID", "ApplicantMemberID", "OwnerID", "ManagerID",
		"PayloadDigest", "IdempotencyKey", "CorrelationID", "Decision",
	} {
		_, ok := payloadType.FieldByName(forbidden)
		require.Falsef(t, ok, "generic Approval already owns %s", forbidden)
	}
	_, executionHasDigest := reflect.TypeOf(executionstore.Execution{}).FieldByName("PayloadDigest")
	require.False(t, executionHasDigest, "Approval.PayloadDigest is the only durable digest authority")
}

func TestSubmitMissPreflightsOutsideCoreCommitThenRechecksIdentityAndManager(t *testing.T) {
	deps := &recordingDependencies{}
	service := newREDService(t, deps)
	service.dependencies.CommitSubmission = func(_ context.Context, commit SubmissionCommit) (approval.Instance, bool, error) {
		deps.record("core-commit-final-auth-manager-recheck-and-create")
		require.Equal(t, redPrincipal(), commit.Principal,
			"the final Core tx must revalidate the authenticated Agent/member")
		require.Equal(t, Manager{UserID: "usr_manager", MemberID: "mem_manager"}, commit.Manager,
			"the final Core tx must require the preflight manager to remain current")
		require.NotEmpty(t, commit.DeterministicJSON)
		require.Len(t, commit.PayloadDigest, 64)
		sum := sha256.Sum256(commit.DeterministicJSON)
		require.Equal(t, hex.EncodeToString(sum[:]), commit.PayloadDigest,
			"Approval deterministic JSON SHA-256 is the sole durable digest")
		require.NotEmpty(t, commit.IdempotencyKey)
		instance := redApproval(t, approval.StatePending)
		instance.Payload, instance.PayloadDigest = commit.DeterministicJSON, commit.PayloadDigest
		return instance, true, nil
	}

	got, err := service.Submit(context.Background(), redSubmit())
	requireRoleServiceImplemented(t, err)
	require.Equal(t, RequestPending, got.Status)
	require.Equal(t, []string{
		"approval-replay-lookup", "authenticate", "resolve-manager",
		"meshx-preflight", "core-commit-final-auth-manager-recheck-and-create",
	}, deps.calls)
}

func TestSubmitExactReplayWinsBeforeMutableRolePermissionAndManagerChecks(t *testing.T) {
	deps := &recordingDependencies{}
	service := newREDService(t, deps)
	description := "new description"
	command := redSubmit()
	command.Proposal = RoleUpdateProposal{
		RoleID: "role_deleted_after_submit", ExpectedRoleVersionID: "rlv_original",
		Description: &description,
	}
	existing := redApprovalForSubmit(t, command, approval.StatePending)
	service.dependencies.LookupApproval = func(context.Context, ApprovalLookup) (approval.Instance, bool, error) {
		deps.record("approval-replay-lookup")
		return existing, true, nil
	}
	service.dependencies.ResolveManager = func(context.Context, AgentPrincipal) (Manager, error) {
		t.Fatal("manager resolution must not run for exact replay after ownership drift")
		return Manager{}, nil
	}
	service.dependencies.Preflight = func(context.Context, PreflightCommand) error {
		t.Fatal("MeshX preflight must not run for exact replay after Role/permission drift")
		return nil
	}

	got, err := service.Submit(context.Background(), command)
	requireRoleServiceImplemented(t, err)
	require.Equal(t, existing.ID, got.ApprovalID)
	require.Equal(t, []string{"approval-replay-lookup"}, deps.calls)
}

func TestOuterReplayRejectsCorruptedSubjectRefOrVersionBeforeMutableChecks(t *testing.T) {
	for _, testCase := range []struct {
		name   string
		mutate func(*approval.Instance)
	}{
		{
			name: "subject ref",
			mutate: func(instance *approval.Instance) {
				instance.SubjectRef = "corrupted"
			},
		},
		{
			name: "subject version",
			mutate: func(instance *approval.Instance) {
				instance.SubjectVersion = 2
			},
		},
	} {
		t.Run(testCase.name, func(t *testing.T) {
			deps := &recordingDependencies{}
			service := newREDService(t, deps)
			command := redSubmit()
			existing := redApprovalForSubmit(t, command, approval.StatePending)
			testCase.mutate(&existing)
			service.dependencies.LookupApproval = func(context.Context, ApprovalLookup) (approval.Instance, bool, error) {
				deps.record("approval-replay-lookup")
				return existing, true, nil
			}
			service.dependencies.AuthenticateApplicant = func(context.Context, agent.AuthenticatedAgent) error {
				t.Fatal("corrupted outer replay must fail before applicant authentication")
				return nil
			}
			service.dependencies.ResolveManager = func(context.Context, AgentPrincipal) (Manager, error) {
				t.Fatal("corrupted outer replay must fail before manager resolution")
				return Manager{}, nil
			}
			service.dependencies.Preflight = func(context.Context, PreflightCommand) error {
				t.Fatal("corrupted outer replay must fail before MeshX preflight")
				return nil
			}

			_, err := service.Submit(context.Background(), command)
			require.ErrorIs(t, err, ErrConflict)
			require.Equal(t, []string{"approval-replay-lookup"}, deps.calls)
		})
	}
}

func TestLookupReplayIsReadOnlyAndNeverRunsMutableSubmissionDependencies(t *testing.T) {
	deps := &recordingDependencies{}
	service := newREDService(t, deps)
	command := redSubmit()
	existing := redApprovalForSubmit(t, command, approval.StatePending)
	service.dependencies.LookupApproval = func(context.Context, ApprovalLookup) (approval.Instance, bool, error) {
		deps.record("approval-replay-lookup")
		return existing, true, nil
	}
	service.dependencies.ResolveManager = func(context.Context, AgentPrincipal) (Manager, error) {
		t.Fatal("read-only replay lookup must not resolve mutable manager facts")
		return Manager{}, nil
	}
	service.dependencies.Preflight = func(context.Context, PreflightCommand) error {
		t.Fatal("read-only replay lookup must not call MeshX")
		return nil
	}
	service.dependencies.CommitSubmission = func(context.Context, SubmissionCommit) (approval.Instance, bool, error) {
		t.Fatal("read-only replay lookup must not open the final Core commit")
		return approval.Instance{}, false, nil
	}

	got, found, err := service.LookupReplay(context.Background(), command)
	require.NoError(t, err)
	require.True(t, found)
	require.Equal(t, existing.ID, got.ApprovalID)
	require.Equal(t, []string{"approval-replay-lookup"}, deps.calls)
}

func TestSubmitRequiresPreflightAndFullTrustedPrincipal(t *testing.T) {
	t.Run("preflight missing", func(t *testing.T) {
		deps := &recordingDependencies{}
		service := newREDService(t, deps)
		service.dependencies.Preflight = nil

		_, err := service.Submit(context.Background(), redSubmit())
		require.ErrorContains(t, err, "not configured")
		require.Empty(t, deps.calls, "configuration must fail before partial submission work")
	})

	t.Run("machine identity missing", func(t *testing.T) {
		deps := &recordingDependencies{}
		service := newREDService(t, deps)
		command := redSubmit()
		command.Principal.MachineID = ""

		_, err := service.Submit(context.Background(), command)
		require.ErrorIs(t, err, ErrForbidden)
		require.Empty(t, deps.calls)
	})
}

func TestSubmitSameIdentityDifferentPayloadConflictsBeforePreflight(t *testing.T) {
	deps := &recordingDependencies{}
	service := newREDService(t, deps)
	service.dependencies.LookupApproval = func(context.Context, ApprovalLookup) (approval.Instance, bool, error) {
		deps.record("approval-replay-lookup")
		return redApproval(t, approval.StatePending), true, nil
	}
	command := redSubmit()
	command.Proposal = RoleCreateProposal{Name: "Different", Definition: "Different"}

	_, err := service.Submit(context.Background(), command)
	if errors.Is(err, ErrNotImplemented) {
		t.Fatalf("RED: %v", err)
	}
	require.ErrorIs(t, err, ErrConflict)
	require.Equal(t, []string{"approval-replay-lookup"}, deps.calls)
}

func TestApproveAndEnqueueShareDecideWithEffectTransaction(t *testing.T) {
	deps := &recordingDependencies{}
	service := newREDService(t, deps)
	tx := &sentinelTx{}
	service.dependencies.EnqueueInTx = func(_ context.Context, gotTx coredb.Tx, workspaceID, approvalID string, _ time.Time) (executionstore.Execution, bool, error) {
		deps.record("enqueue-in-decision-tx")
		require.Same(t, tx, gotTx)
		require.Equal(t, "ws_1", workspaceID)
		require.Equal(t, "appr_1", approvalID)
		return executionstore.Execution{
			ApprovalID: approvalID, WorkspaceID: workspaceID, Status: executionstore.ExecutionQueued,
		}, true, nil
	}
	service.dependencies.DecideWithEffect = func(ctx context.Context, params approval.DecideParams, effect approval.DecisionEffect) (approval.DecisionResult, error) {
		deps.record("decide-with-effect")
		instance := redApproval(t, approval.StatePending)
		_, err := effect(ctx, tx, instance, params.Decision)
		require.NoError(t, err)
		instance.State = approval.StateApproved
		return approval.DecisionResult{Instance: instance}, nil
	}

	got, err := service.Decide(context.Background(), approval.DecideParams{
		WorkspaceID: "ws_1", ApprovalID: "appr_1", Decision: approval.DecisionApproved,
		ActorMemberID: "mem_manager", ClientDecisionID: "decision_1", Now: time.Unix(100, 0).UTC(),
	})
	requireRoleServiceImplemented(t, err)
	require.Equal(t, RequestProcessing, got.Status)
	require.Equal(t, []string{"decide-with-effect", "enqueue-in-decision-tx"}, deps.calls)
}

func TestRejectEnqueuesNothingAndApproveReplayLoadsExistingExecution(t *testing.T) {
	t.Run("reject", func(t *testing.T) {
		deps := &recordingDependencies{}
		service := newREDService(t, deps)
		service.dependencies.EnqueueInTx = func(context.Context, coredb.Tx, string, string, time.Time) (executionstore.Execution, bool, error) {
			t.Fatal("reject must not enqueue")
			return executionstore.Execution{}, false, nil
		}
		service.dependencies.DecideWithEffect = func(ctx context.Context, params approval.DecideParams, effect approval.DecisionEffect) (approval.DecisionResult, error) {
			instance := redApproval(t, approval.StatePending)
			_, err := effect(ctx, &sentinelTx{}, instance, params.Decision)
			require.NoError(t, err)
			instance.State = approval.StateRejected
			return approval.DecisionResult{Instance: instance}, nil
		}
		got, err := service.Decide(context.Background(), approval.DecideParams{
			WorkspaceID: "ws_1", ApprovalID: "appr_1", Decision: approval.DecisionRejected,
		})
		requireRoleServiceImplemented(t, err)
		require.Equal(t, RequestRejected, got.Status)
	})

	t.Run("approve replay", func(t *testing.T) {
		deps := &recordingDependencies{}
		service := newREDService(t, deps)
		service.dependencies.DecideWithEffect = func(context.Context, approval.DecideParams, approval.DecisionEffect) (approval.DecisionResult, error) {
			deps.record("decision-replay")
			return approval.DecisionResult{
				Instance: redApproval(t, approval.StateApproved), AlreadyApplied: true,
			}, nil
		}
		got, err := service.Decide(context.Background(), approval.DecideParams{
			WorkspaceID: "ws_1", ApprovalID: "appr_1", Decision: approval.DecisionApproved,
		})
		requireRoleServiceImplemented(t, err)
		require.Equal(t, RequestProcessing, got.Status)
		require.Equal(t, []string{"decision-replay", "load-execution"}, deps.calls)
	})
}

func TestApproveReplayWithoutExecutionIsInvariantError(t *testing.T) {
	deps := &recordingDependencies{}
	service := newREDService(t, deps)
	service.dependencies.DecideWithEffect = func(context.Context, approval.DecideParams, approval.DecisionEffect) (approval.DecisionResult, error) {
		return approval.DecisionResult{
			Instance: redApproval(t, approval.StateApproved), AlreadyApplied: true,
		}, nil
	}
	service.dependencies.LoadExecution = func(context.Context, string, string) (executionstore.Execution, error) {
		return executionstore.Execution{}, executionstore.ErrNotFound
	}

	_, err := service.Decide(context.Background(), approval.DecideParams{
		WorkspaceID: "ws_1", ApprovalID: "appr_1", Decision: approval.DecisionApproved,
	})
	if errors.Is(err, ErrNotImplemented) {
		t.Fatalf("RED: %v", err)
	}
	require.ErrorIs(t, err, ErrInvariant)
}

func TestRequestGetProjectsAllSixStatesWithoutCreatingOrRetryingWork(t *testing.T) {
	cases := []struct {
		name          string
		approvalState string
		execution     *executionstore.Execution
		want          RequestStatus
	}{
		{name: "pending", approvalState: approval.StatePending, want: RequestPending},
		{name: "queued", approvalState: approval.StateApproved, execution: &executionstore.Execution{Status: executionstore.ExecutionQueued}, want: RequestProcessing},
		{name: "running", approvalState: approval.StateApproved, execution: &executionstore.Execution{Status: executionstore.ExecutionRunning}, want: RequestProcessing},
		{name: "succeeded", approvalState: approval.StateApproved, execution: &executionstore.Execution{Status: executionstore.ExecutionSucceeded}, want: RequestSucceeded},
		{name: "rejected", approvalState: approval.StateRejected, want: RequestRejected},
		{name: "failed", approvalState: approval.StateApproved, execution: &executionstore.Execution{Status: executionstore.ExecutionFailed}, want: RequestFailed},
		{name: "cancelled", approvalState: approval.StateCancelled, want: RequestCancelled},
	}
	for _, testCase := range cases {
		t.Run(testCase.name, func(t *testing.T) {
			deps := &recordingDependencies{}
			service := newREDService(t, deps)
			service.dependencies.LookupApproval = func(context.Context, ApprovalLookup) (approval.Instance, bool, error) {
				deps.record("approval-replay-lookup")
				return redApproval(t, testCase.approvalState), true, nil
			}
			service.dependencies.LoadExecution = func(context.Context, string, string) (executionstore.Execution, error) {
				deps.record("load-execution")
				if testCase.execution == nil {
					return executionstore.Execution{}, executionstore.ErrNotFound
				}
				got := *testCase.execution
				got.ApprovalID, got.WorkspaceID = "appr_1", "ws_1"
				return got, nil
			}
			forbidRequestGetMutations(t, service)

			got, err := service.RequestGet(context.Background(), RequestGetCommand{
				Principal: AgentPrincipal(redPrincipal()), RequestID: "req_1",
			})
			requireRoleServiceImplemented(t, err)
			require.Equal(t, testCase.want, got.Status)
			require.Equal(t, "agt_1", got.ApplicantAgentID)
			wantCalls := []string{"authenticate", "approval-replay-lookup"}
			if testCase.approvalState == approval.StateApproved {
				wantCalls = append(wantCalls, "load-execution")
			}
			require.Equal(t, wantCalls, deps.calls,
				"request-get must authenticate an active Agent/member before its read-only ledger projection")
		})
	}
}

func TestRequestGetRejectsCrossAgentPayloadAndApprovedWithoutExecution(t *testing.T) {
	t.Run("same Agent scope", func(t *testing.T) {
		deps := &recordingDependencies{}
		service := newREDService(t, deps)
		instance := redApproval(t, approval.StatePending)
		var payload ApprovalPayload
		require.NoError(t, json.Unmarshal(instance.Payload, &payload))
		payload.ApplicantAgentID = "agt_other"
		raw, err := json.Marshal(payload)
		require.NoError(t, err)
		instance.Payload, err = deterministicJSONBytes(raw)
		require.NoError(t, err)
		sum := sha256.Sum256(instance.Payload)
		instance.PayloadDigest = hex.EncodeToString(sum[:])
		service.dependencies.LookupApproval = func(context.Context, ApprovalLookup) (approval.Instance, bool, error) {
			return instance, true, nil
		}
		_, err = service.RequestGet(context.Background(), RequestGetCommand{
			Principal: AgentPrincipal(redPrincipal()), RequestID: "req_1",
		})
		if errors.Is(err, ErrNotImplemented) {
			t.Fatalf("RED: %v", err)
		}
		require.ErrorIs(t, err, ErrForbidden)
	})

	t.Run("approved invariant", func(t *testing.T) {
		deps := &recordingDependencies{}
		service := newREDService(t, deps)
		service.dependencies.LookupApproval = func(context.Context, ApprovalLookup) (approval.Instance, bool, error) {
			return redApproval(t, approval.StateApproved), true, nil
		}
		service.dependencies.LoadExecution = func(context.Context, string, string) (executionstore.Execution, error) {
			return executionstore.Execution{}, executionstore.ErrNotFound
		}
		_, err := service.RequestGet(context.Background(), RequestGetCommand{
			Principal: AgentPrincipal(redPrincipal()), RequestID: "req_1",
		})
		if errors.Is(err, ErrNotImplemented) {
			t.Fatalf("RED: %v", err)
		}
		require.ErrorIs(t, err, ErrInvariant)
	})

	t.Run("payload request identity", func(t *testing.T) {
		deps := &recordingDependencies{}
		service := newREDService(t, deps)
		instance := redApproval(t, approval.StatePending)
		var payload ApprovalPayload
		require.NoError(t, json.Unmarshal(instance.Payload, &payload))
		payload.RequestID = "req_other"
		instance.Payload, _ = json.Marshal(payload)
		sum := sha256.Sum256(instance.Payload)
		instance.PayloadDigest = hex.EncodeToString(sum[:])
		service.dependencies.LookupApproval = func(context.Context, ApprovalLookup) (approval.Instance, bool, error) {
			deps.record("approval-replay-lookup")
			return instance, true, nil
		}
		forbidRequestGetMutations(t, service)

		_, err := service.RequestGet(context.Background(), RequestGetCommand{
			Principal: AgentPrincipal(redPrincipal()), RequestID: "req_1",
		})
		if errors.Is(err, ErrNotImplemented) {
			t.Fatalf("RED: %v", err)
		}
		require.ErrorIs(t, err, ErrInvariant,
			"the loaded Approval payload must match the requested requestId")
		require.Equal(t, []string{"authenticate", "approval-replay-lookup"}, deps.calls)
	})
}

func TestValidateApprovalInstanceRejectsMalformedActionUnion(t *testing.T) {
	validCreate := &RoleCreateProposal{Name: "Release", Definition: "Review"}
	validUpdate := &RoleUpdateProposal{
		RoleID: "role_1", ExpectedRoleVersionID: "rlv_1", Visibility: "private",
	}
	validDelete := &RoleDeleteProposal{RoleID: "role_1", ExpectedRoleVersionID: "rlv_1"}
	testCases := []struct {
		name    string
		payload ApprovalPayload
	}{
		{
			name: "unknown action",
			payload: ApprovalPayload{
				SchemaVersion: PayloadSchemaVersion, RequestID: "req_1",
				ApplicantAgentID: "agt_1", Action: RoleRequestAction("unknown"),
				Create: validCreate,
			},
		},
		{
			name: "zero proposal branches",
			payload: ApprovalPayload{
				SchemaVersion: PayloadSchemaVersion, RequestID: "req_1",
				ApplicantAgentID: "agt_1", Action: ActionCreate,
			},
		},
		{
			name: "multiple proposal branches",
			payload: ApprovalPayload{
				SchemaVersion: PayloadSchemaVersion, RequestID: "req_1",
				ApplicantAgentID: "agt_1", Action: ActionCreate,
				Create: validCreate, Delete: validDelete,
			},
		},
		{
			name: "action branch mismatch",
			payload: ApprovalPayload{
				SchemaVersion: PayloadSchemaVersion, RequestID: "req_1",
				ApplicantAgentID: "agt_1", Action: ActionCreate,
				Update: validUpdate,
			},
		},
		{
			name: "illegal branch fields",
			payload: ApprovalPayload{
				SchemaVersion: PayloadSchemaVersion, RequestID: "req_1",
				ApplicantAgentID: "agt_1", Action: ActionCreate,
				Create: &RoleCreateProposal{Name: " ", Definition: "Review"},
			},
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			instance := redApproval(t, approval.StatePending)
			raw, err := json.Marshal(testCase.payload)
			require.NoError(t, err)
			instance.Payload = raw
			sum := sha256.Sum256(raw)
			instance.PayloadDigest = hex.EncodeToString(sum[:])

			require.NotPanics(t, func() {
				err = ValidateApprovalInstance(instance)
			})
			require.ErrorIs(t, err, ErrInvariant)
		})
	}
}

func TestMalformedDurablePayloadFailsClosedAcrossDecideAndRequestGet(t *testing.T) {
	instance := redApproval(t, approval.StatePending)
	payload := ApprovalPayload{
		SchemaVersion: PayloadSchemaVersion, RequestID: "req_1",
		ApplicantAgentID: "agt_1", Action: ActionCreate,
	}
	var err error
	instance.Payload, err = json.Marshal(payload)
	require.NoError(t, err)
	sum := sha256.Sum256(instance.Payload)
	instance.PayloadDigest = hex.EncodeToString(sum[:])

	t.Run("decide", func(t *testing.T) {
		deps := &recordingDependencies{}
		service := newREDService(t, deps)
		enqueued := false
		service.dependencies.EnqueueInTx = func(
			context.Context,
			coredb.Tx,
			string,
			string,
			time.Time,
		) (executionstore.Execution, bool, error) {
			enqueued = true
			return executionstore.Execution{
				ApprovalID: instance.ID, WorkspaceID: instance.WorkspaceID,
				Status: executionstore.ExecutionQueued,
			}, true, nil
		}
		service.dependencies.DecideWithEffect = func(
			ctx context.Context,
			params approval.DecideParams,
			effect approval.DecisionEffect,
		) (approval.DecisionResult, error) {
			_, effectErr := effect(ctx, &sentinelTx{}, instance, params.Decision)
			if effectErr != nil {
				return approval.DecisionResult{}, effectErr
			}
			decided := instance
			decided.State = approval.StateApproved
			return approval.DecisionResult{Instance: decided}, nil
		}

		require.NotPanics(t, func() {
			_, err = service.Decide(context.Background(), approval.DecideParams{
				WorkspaceID: "ws_1", ApprovalID: instance.ID,
				Decision: approval.DecisionApproved,
			})
		})
		require.ErrorIs(t, err, ErrInvariant)
		require.False(t, enqueued, "malformed payload must fail before enqueue")
	})

	t.Run("request get", func(t *testing.T) {
		deps := &recordingDependencies{}
		service := newREDService(t, deps)
		service.dependencies.LookupApproval = func(context.Context, ApprovalLookup) (approval.Instance, bool, error) {
			return instance, true, nil
		}

		require.NotPanics(t, func() {
			_, err = service.RequestGet(context.Background(), RequestGetCommand{
				Principal: AgentPrincipal(redPrincipal()), RequestID: "req_1",
			})
		})
		require.ErrorIs(t, err, ErrInvariant)
	})
}
