package roles

import (
	"context"
	"encoding/json"
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	rolesstore "github.com/tidbcloud/AgentTeam/internal/store/resource/roles"
)

func TestRecoverRoleOperationReadsOnlyMatchingStableReceipt(t *testing.T) {
	svc := newRoleServiceForTest(t, Options{})
	ctx := context.Background()
	const digest = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	for index, testCase := range []struct {
		action string
		result string
	}{
		{roleOperationCreate, `{"roleId":"role_create","roleVersionId":"rlv_create"}`},
		{roleOperationUpdate, `{"roleId":"role_update","roleVersionId":"rlv_update"}`},
		{roleOperationDelete, `{"roleId":"role_delete","roleVersionId":"rlv_delete"}`},
		{roleOperationApply, `{"realizationId":"rrl_apply","roleId":"role_apply","roleVersionId":"rlv_apply","agentId":"agt_apply"}`},
	} {
		operationKey := fmt.Sprintf("op_%d", index)
		require.NoError(t, svc.store.DB().Create(&rolesstore.RoleOperationReceipt{
			WorkspaceID: "wsp_1", OperationKey: operationKey,
			Action: testCase.action, RequestDigest: digest,
			ResultJSON: testCase.result, CreatedAt: time.Now().UTC(),
		}).Error)
		before := roleFactSnapshotForTest(t, svc)

		recovered, err := svc.RecoverRoleOperation(
			ctx,
			"wsp_1",
			testCase.action,
			MutationOperation{OperationKey: operationKey, RequestDigest: digest},
		)

		require.NoError(t, err)
		require.True(t, recovered.Found)
		require.Equal(t, "role_"+testCase.action, recovered.RoleID)
		require.Equal(t, "rlv_"+testCase.action, recovered.RoleVersionID)
		require.Equal(t, before, roleFactSnapshotForTest(t, svc),
			"receipt recovery must not mutate Role facts")

		miss, err := svc.RecoverRoleOperation(
			ctx,
			"wsp_other",
			testCase.action,
			MutationOperation{OperationKey: operationKey, RequestDigest: digest},
		)
		require.NoError(t, err)
		require.Equal(t, RoleOperationRecovery{}, miss)
	}
}

func TestRecoverRoleOperationConflictsBeforeDecodingAndRejectsInvalidReceipts(t *testing.T) {
	svc := newRoleServiceForTest(t, Options{})
	ctx := context.Background()
	const digest = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	require.NoError(t, svc.store.DB().Create(&rolesstore.RoleOperationReceipt{
		WorkspaceID: "wsp_1", OperationKey: "op_conflict",
		Action: roleOperationCreate, RequestDigest: digest,
		ResultJSON: `{`, CreatedAt: time.Now().UTC(),
	}).Error)

	for _, operation := range []struct {
		action string
		digest string
	}{
		{roleOperationUpdate, digest},
		{roleOperationCreate, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"},
	} {
		_, err := svc.RecoverRoleOperation(
			ctx, "wsp_1", operation.action,
			MutationOperation{OperationKey: "op_conflict", RequestDigest: operation.digest},
		)
		require.ErrorIs(t, err, ErrRoleMutationConflict)
	}

	for index, result := range []string{
		`{`,
		`{"roleId":"","roleVersionId":"rlv_1"}`,
		`{"roleId":"role_1","roleVersionId":""}`,
		`{"roleId":"role_other","roleId":"role_1","roleVersionId":"rlv_1"}`,
		`{"roleId":"role_1","roleVersionId":"rlv_1","extra":true}`,
		`{"roleId":"role_1","roleVersionId":"rlv_1"} {}`,
	} {
		operationKey := fmt.Sprintf("op_bad_%d", index)
		require.NoError(t, svc.store.DB().Create(&rolesstore.RoleOperationReceipt{
			WorkspaceID: "wsp_1", OperationKey: operationKey,
			Action: roleOperationCreate, RequestDigest: digest,
			ResultJSON: result, CreatedAt: time.Now().UTC(),
		}).Error)
		recovered, err := svc.RecoverRoleOperation(
			ctx, "wsp_1", roleOperationCreate,
			MutationOperation{OperationKey: operationKey, RequestDigest: digest},
		)
		require.Error(t, err)
		require.Equal(t, RoleOperationRecovery{}, recovered)
	}

	for index, result := range []string{
		`{"roleId":"role_1","roleVersionId":"rlv_1","agentId":"agt_1"}`,
		`{"realizationId":"rrl_1","roleId":"role_1","roleVersionId":"rlv_1"}`,
	} {
		operationKey := fmt.Sprintf("op_bad_apply_%d", index)
		require.NoError(t, svc.store.DB().Create(&rolesstore.RoleOperationReceipt{
			WorkspaceID: "wsp_1", OperationKey: operationKey,
			Action: roleOperationApply, RequestDigest: digest,
			ResultJSON: result, CreatedAt: time.Now().UTC(),
		}).Error)
		recovered, err := svc.RecoverRoleOperation(
			ctx, "wsp_1", roleOperationApply,
			MutationOperation{OperationKey: operationKey, RequestDigest: digest},
		)
		require.Error(t, err)
		require.Equal(t, RoleOperationRecovery{}, recovered)
	}
}

func TestRecoverRoleOperationRejectsMalformedTypedIDsForEveryAction(t *testing.T) {
	svc := newRoleServiceForTest(t, Options{})
	ctx := context.Background()
	const digest = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	for index, testCase := range []struct {
		action string
		result string
	}{
		{roleOperationCreate, `{"roleId":"???","roleVersionId":"rlv_create"}`},
		{roleOperationUpdate, `{"roleId":"role_update","roleVersionId":"role_wrong_type"}`},
		{roleOperationDelete, `{"roleId":"rlv_wrong_type","roleVersionId":"rlv_delete"}`},
		{roleOperationApply, `{"realizationId":"bad","roleId":"role_apply","roleVersionId":"rlv_apply","agentId":"agt_apply"}`},
		{roleOperationApply, `{"realizationId":"rrl_apply","roleId":"role_apply","roleVersionId":"rlv_apply","agentId":"usr_wrong_type"}`},
	} {
		operationKey := fmt.Sprintf("op_malformed_typed_%d", index)
		require.NoError(t, svc.store.DB().Create(&rolesstore.RoleOperationReceipt{
			WorkspaceID: "wsp_1", OperationKey: operationKey,
			Action: testCase.action, RequestDigest: digest,
			ResultJSON: testCase.result, CreatedAt: time.Now().UTC(),
		}).Error)

		recovered, err := svc.RecoverRoleOperation(
			ctx, "wsp_1", testCase.action,
			MutationOperation{OperationKey: operationKey, RequestDigest: digest},
		)

		require.Error(t, err)
		require.Equal(t, RoleOperationRecovery{}, recovered)
	}
}

func TestRoleOperationReceiptsStoreOnlyTypedStableIDs(t *testing.T) {
	svc := newRoleServiceForTest(t, Options{
		Ownership: &testAgentOwnershipSource{owned: map[string]bool{"agt_sensitive": true}},
	})
	ctx := context.Background()

	created, err := svc.CreateRoleOperation(
		ctx, "wsp_1", "usr_1",
		MutationOperation{OperationKey: "receipt-create", RequestDigest: digestForTest("create")},
		Mutation{Name: "Sensitive name", Definition: "sensitive create definition"},
	)
	require.NoError(t, err)
	requireReceiptJSON(t, svc, "receipt-create", map[string]string{
		"roleId": created.ID, "roleVersionId": created.RoleVersionID,
	})

	definition := "sensitive updated definition"
	updated, err := svc.UpdateRoleOperation(
		ctx, "wsp_1", "usr_1", created.ID,
		MutationOperation{OperationKey: "receipt-update", RequestDigest: digestForTest("update")},
		created.RoleVersionID, RoleUpdatePatch{Definition: &definition},
	)
	require.NoError(t, err)
	requireReceiptJSON(t, svc, "receipt-update", map[string]string{
		"roleId": updated.ID, "roleVersionId": updated.RoleVersionID,
	})

	applied, err := svc.ApplyRoleOperation(
		ctx, "wsp_1", "usr_1", created.ID,
		MutationOperation{OperationKey: "receipt-apply", RequestDigest: digestForTest("apply")},
		RealizationRequest{AgentID: "agt_sensitive", RoleVersionID: updated.RoleVersionID, Apply: true},
	)
	require.NoError(t, err)
	var realization rolesstore.RoleRealization
	require.NoError(t, svc.store.DB().Where("role_id = ?", created.ID).First(&realization).Error)
	requireReceiptJSON(t, svc, "receipt-apply", map[string]string{
		"realizationId": realization.ID,
		"roleId":        created.ID,
		"roleVersionId": updated.RoleVersionID,
		"agentId":       "agt_sensitive",
	})
	replayed, err := svc.ApplyRoleOperation(
		ctx, "wsp_1", "usr_1", created.ID,
		MutationOperation{OperationKey: "receipt-apply", RequestDigest: digestForTest("apply")},
		RealizationRequest{AgentID: "agt_sensitive", RoleVersionID: updated.RoleVersionID, Apply: true},
	)
	require.NoError(t, err)
	requireStableApplyReceiptReplay(t, applied, replayed)

	deleted, err := svc.DeleteRoleOperation(
		ctx, "wsp_1", "usr_1", created.ID,
		MutationOperation{OperationKey: "receipt-delete", RequestDigest: digestForTest("delete")},
		updated.RoleVersionID,
	)
	require.NoError(t, err)
	requireReceiptJSON(t, svc, "receipt-delete", map[string]string{
		"roleId": deleted.RoleID, "roleVersionId": deleted.RoleVersionID,
	})
}

func TestCreateReceiptReplayReturnsStableIDsAfterRoleSoftDelete(t *testing.T) {
	svc := newRoleServiceForTest(t, Options{})
	ctx := context.Background()
	operation := MutationOperation{
		OperationKey: "create-before-soft-delete", RequestDigest: digestForTest("create"),
	}
	input := Mutation{Name: "Reviewer", Definition: "review"}
	created, err := svc.CreateRoleOperation(ctx, "wsp_1", "usr_1", operation, input)
	require.NoError(t, err)

	_, err = svc.DeleteRoleOperation(
		ctx, "wsp_1", "usr_1", created.ID,
		MutationOperation{
			OperationKey: "delete-after-create", RequestDigest: digestForTest("delete"),
		},
		created.RoleVersionID,
	)
	require.NoError(t, err)
	_, err = svc.GetRole(ctx, "wsp_1", "usr_1", created.ID)
	require.Error(t, err)

	replayed, err := svc.CreateRoleOperation(ctx, "wsp_1", "usr_1", operation, input)
	require.NoError(t, err)
	require.Equal(t, created.ID, replayed.ID)
	require.Equal(t, created.RoleVersionID, replayed.RoleVersionID)
}

func TestUpdateReceiptReplayReturnsStableIDsAfterManagerGrantRevocation(t *testing.T) {
	svc := newRoleServiceForTest(t, Options{})
	ctx := context.Background()
	role, err := svc.CreateRole(ctx, "wsp_1", "usr_owner", Mutation{
		Name: "Reviewer", Definition: "review",
	})
	require.NoError(t, err)
	now := svc.now()
	_, err = svc.store.UpsertRoleGrant(ctx, rolesstore.RoleGrant{
		ID: "grant_manager", WorkspaceID: "wsp_1", RoleID: role.ID,
		SubjectType: "user", SubjectID: "usr_manager", Role: roleGrantManage,
		CreatedBy: "usr_owner", UpdatedBy: "usr_owner", CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)
	operation := MutationOperation{
		OperationKey: "update-before-grant-revoke", RequestDigest: digestForTest("update"),
	}
	description := "receipt result"
	updated, err := svc.UpdateRoleOperation(
		ctx, "wsp_1", "usr_manager", role.ID, operation, role.RoleVersionID,
		RoleUpdatePatch{Description: &description},
	)
	require.NoError(t, err)

	require.NoError(t, svc.store.DB().
		Where("id = ?", "grant_manager").
		Delete(&rolesstore.RoleGrant{}).Error)

	replayed, err := svc.UpdateRoleOperation(
		ctx, "wsp_1", "usr_manager", role.ID, operation, role.RoleVersionID,
		RoleUpdatePatch{Description: &description},
	)
	require.NoError(t, err)
	require.Equal(t, updated.ID, replayed.ID)
	require.Equal(t, updated.RoleVersionID, replayed.RoleVersionID)
}

func TestApplyReceiptReplayReturnsStableIDsAfterAgentOwnershipDrift(t *testing.T) {
	ownership := &testAgentOwnershipSource{owned: map[string]bool{"agt_1": true}}
	svc := newRoleServiceForTest(t, Options{Ownership: ownership})
	ctx := context.Background()
	role, err := svc.CreateRole(ctx, "wsp_1", "usr_1", Mutation{Name: "Reviewer", Definition: "review"})
	require.NoError(t, err)
	operation := MutationOperation{OperationKey: "apply-current-ownership", RequestDigest: digestForTest("apply")}
	input := RealizationRequest{AgentID: "agt_1", RoleVersionID: role.RoleVersionID, Apply: true}
	applied, err := svc.ApplyRoleOperation(ctx, "wsp_1", "usr_1", role.ID, operation, input)
	require.NoError(t, err)

	ownership.owned["agt_1"] = false
	replayed, err := svc.ApplyRoleOperation(ctx, "wsp_1", "usr_1", role.ID, operation, input)
	require.NoError(t, err)
	require.Equal(t, applied.RoleID, replayed.RoleID)
	require.Equal(t, applied.RoleVersionID(), replayed.RoleVersionID())
	require.Equal(t, applied.AgentID, replayed.AgentID)
}

func TestApplyReceiptReplayDoesNotRequireReceiptVersionToRemainActive(t *testing.T) {
	ownership := &testAgentOwnershipSource{owned: map[string]bool{"agt_1": true}}
	svc := newRoleServiceForTest(t, Options{Ownership: ownership})
	ctx := context.Background()
	role, err := svc.CreateRole(ctx, "wsp_1", "usr_1", Mutation{Name: "Reviewer", Definition: "review"})
	require.NoError(t, err)
	operation := MutationOperation{OperationKey: "apply-inactive-version", RequestDigest: digestForTest("apply")}
	input := RealizationRequest{AgentID: "agt_1", RoleVersionID: role.RoleVersionID, Apply: true}
	applied, err := svc.ApplyRoleOperation(ctx, "wsp_1", "usr_1", role.ID, operation, input)
	require.NoError(t, err)

	description := "advanced after apply"
	updated, err := svc.UpdateRoleOperation(
		ctx, "wsp_1", "usr_1", role.ID,
		MutationOperation{OperationKey: "advance-after-apply", RequestDigest: digestForTest("advance")},
		role.RoleVersionID, RoleUpdatePatch{Description: &description},
	)
	require.NoError(t, err)
	require.NotEqual(t, role.RoleVersionID, updated.RoleVersionID)

	replayed, err := svc.ApplyRoleOperation(ctx, "wsp_1", "usr_1", role.ID, operation, input)
	require.NoError(t, err)
	requireStableApplyReceiptReplay(t, applied, replayed)

	_, err = svc.ApplyRoleOperation(ctx, "wsp_1", "usr_1", role.ID, MutationOperation{
		OperationKey: "fresh-old-version", RequestDigest: digestForTest("fresh-old"),
	}, input)
	require.ErrorIs(t, err, ErrRoleVersionConflict)
}

func TestApplyReceiptReplayReturnsStableIDsAfterRoleGrantRevocation(t *testing.T) {
	ownership := &testAgentOwnershipSource{owned: map[string]bool{"agt_1": true}}
	svc := newRoleServiceForTest(t, Options{Ownership: ownership})
	ctx := context.Background()
	role, err := svc.CreateRole(ctx, "wsp_1", "usr_owner", Mutation{Name: "Reviewer", Definition: "review"})
	require.NoError(t, err)
	now := svc.now()
	_, err = svc.store.UpsertRoleGrant(ctx, rolesstore.RoleGrant{
		ID: "grant_reader", WorkspaceID: "wsp_1", RoleID: role.ID,
		SubjectType: "user", SubjectID: "usr_reader", Role: roleGrantView,
		CreatedBy: "usr_owner", UpdatedBy: "usr_owner", CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)
	operation := MutationOperation{OperationKey: "apply-current-read", RequestDigest: digestForTest("apply")}
	input := RealizationRequest{AgentID: "agt_1", RoleVersionID: role.RoleVersionID, Apply: true}
	applied, err := svc.ApplyRoleOperation(ctx, "wsp_1", "usr_reader", role.ID, operation, input)
	require.NoError(t, err)

	require.NoError(t, svc.store.DB().Where("id = ?", "grant_reader").Delete(&rolesstore.RoleGrant{}).Error)
	replayed, err := svc.ApplyRoleOperation(ctx, "wsp_1", "usr_reader", role.ID, operation, input)
	require.NoError(t, err)
	require.Equal(t, applied.RoleID, replayed.RoleID)
	require.Equal(t, applied.RoleVersionID(), replayed.RoleVersionID())
	require.Equal(t, applied.AgentID, replayed.AgentID)
}

func TestRoleOperationCompletedReceiptReplayPrecedesFreshAdmissionAfterDrift(t *testing.T) {
	t.Run("create", func(t *testing.T) {
		svc := newRoleServiceForTest(t, Options{})
		ctx := context.Background()
		operation := MutationOperation{OperationKey: "drift-create", RequestDigest: digestForTest("create")}
		input := Mutation{Name: "Reviewer", Definition: "review v1"}
		created, err := svc.CreateRoleOperation(ctx, "wsp_1", "usr_1", operation, input)
		require.NoError(t, err)

		description := "active version drift"
		current, err := svc.UpdateRoleOperation(
			ctx, "wsp_1", "usr_1", created.ID,
			MutationOperation{OperationKey: "drift-create-advance", RequestDigest: digestForTest("advance")},
			created.RoleVersionID, RoleUpdatePatch{Description: &description},
		)
		require.NoError(t, err)
		require.NotEqual(t, created.RoleVersionID, current.RoleVersionID)
		beforeReplay := roleFactSnapshotForTest(t, svc)

		replayed, err := svc.CreateRoleOperation(ctx, "wsp_1", "usr_1", operation, input)
		require.NoError(t, err)
		require.Equal(t, created.RoleVersionID, replayed.RoleVersionID)
		require.Equal(t, beforeReplay, roleFactSnapshotForTest(t, svc))
	})

	t.Run("update", func(t *testing.T) {
		svc := newRoleServiceForTest(t, Options{})
		ctx := context.Background()
		role, err := svc.CreateRole(ctx, "wsp_1", "usr_1", Mutation{Name: "Reviewer", Definition: "review v1"})
		require.NoError(t, err)
		operation := MutationOperation{OperationKey: "drift-update", RequestDigest: digestForTest("update")}
		description := "receipt version"
		updated, err := svc.UpdateRoleOperation(
			ctx, "wsp_1", "usr_1", role.ID, operation, role.RoleVersionID,
			RoleUpdatePatch{Description: &description},
		)
		require.NoError(t, err)

		advancedDescription := "active version drift"
		current, err := svc.UpdateRoleOperation(
			ctx, "wsp_1", "usr_1", role.ID,
			MutationOperation{OperationKey: "drift-update-advance", RequestDigest: digestForTest("advance")},
			updated.RoleVersionID, RoleUpdatePatch{Description: &advancedDescription},
		)
		require.NoError(t, err)
		require.NotEqual(t, updated.RoleVersionID, current.RoleVersionID)
		beforeReplay := roleFactSnapshotForTest(t, svc)

		replayed, err := svc.UpdateRoleOperation(
			ctx, "wsp_1", "usr_1", role.ID, operation, role.RoleVersionID,
			RoleUpdatePatch{Description: &description},
		)
		require.NoError(t, err)
		require.Equal(t, updated.RoleVersionID, replayed.RoleVersionID)
		require.Equal(t, beforeReplay, roleFactSnapshotForTest(t, svc))
	})

	t.Run("delete", func(t *testing.T) {
		svc := newRoleServiceForTest(t, Options{})
		ctx := context.Background()
		role, err := svc.CreateRole(ctx, "wsp_1", "usr_1", Mutation{Name: "Reviewer", Definition: "review"})
		require.NoError(t, err)
		operation := MutationOperation{OperationKey: "drift-delete", RequestDigest: digestForTest("delete")}
		deleted, err := svc.DeleteRoleOperation(
			ctx, "wsp_1", "usr_1", role.ID, operation, role.RoleVersionID,
		)
		require.NoError(t, err)
		_, err = svc.GetRole(ctx, "wsp_1", "usr_1", role.ID)
		require.Error(t, err)
		beforeReplay := roleFactSnapshotForTest(t, svc)

		replayed, err := svc.DeleteRoleOperation(
			ctx, "wsp_1", "usr_1", role.ID, operation, role.RoleVersionID,
		)
		require.NoError(t, err)
		require.Equal(t, deleted, replayed)
		require.Equal(t, beforeReplay, roleFactSnapshotForTest(t, svc))
	})

	t.Run("apply", func(t *testing.T) {
		ownership := &testAgentOwnershipSource{owned: map[string]bool{"agt_1": true}}
		svc := newRoleServiceForTest(t, Options{Ownership: ownership})
		ctx := context.Background()
		role, err := svc.CreateRole(ctx, "wsp_1", "usr_1", Mutation{Name: "Reviewer", Definition: "review v1"})
		require.NoError(t, err)
		operation := MutationOperation{OperationKey: "drift-apply", RequestDigest: digestForTest("apply")}
		input := RealizationRequest{AgentID: "agt_1", RoleVersionID: role.RoleVersionID, Apply: true}
		applied, err := svc.ApplyRoleOperation(ctx, "wsp_1", "usr_1", role.ID, operation, input)
		require.NoError(t, err)

		description := "active version drift"
		current, err := svc.UpdateRoleOperation(
			ctx, "wsp_1", "usr_1", role.ID,
			MutationOperation{OperationKey: "drift-apply-advance", RequestDigest: digestForTest("advance")},
			role.RoleVersionID, RoleUpdatePatch{Description: &description},
		)
		require.NoError(t, err)
		require.NotEqual(t, role.RoleVersionID, current.RoleVersionID)
		beforeReplay := roleFactSnapshotForTest(t, svc)

		replayed, err := svc.ApplyRoleOperation(ctx, "wsp_1", "usr_1", role.ID, operation, input)
		require.NoError(t, err)
		requireStableApplyReceiptReplay(t, applied, replayed)
		require.Equal(t, beforeReplay, roleFactSnapshotForTest(t, svc))
	})
}

func TestRoleOperationReceiptConflictPrecedesFreshAdmissionForAllActions(t *testing.T) {
	ownership := &testAgentOwnershipSource{owned: map[string]bool{"agt_1": true}}
	svc := newRoleServiceForTest(t, Options{Ownership: ownership})
	ctx := context.Background()

	created, err := svc.CreateRoleOperation(
		ctx, "wsp_1", "usr_1",
		MutationOperation{OperationKey: "conflict-create", RequestDigest: digestForTest("create")},
		Mutation{Name: "Created", Definition: "created"},
	)
	require.NoError(t, err)
	_, err = svc.CreateRoleOperation(
		ctx, "wsp_1", "usr_1",
		MutationOperation{OperationKey: "conflict-create", RequestDigest: digestForTest("different")},
		Mutation{},
	)
	require.ErrorIs(t, err, ErrRoleMutationConflict)

	name := "Updated"
	updated, err := svc.UpdateRoleOperation(
		ctx, "wsp_1", "usr_1", created.ID,
		MutationOperation{OperationKey: "conflict-update", RequestDigest: digestForTest("update")},
		created.RoleVersionID, RoleUpdatePatch{Name: &name},
	)
	require.NoError(t, err)
	_, err = svc.UpdateRoleOperation(
		ctx, "wsp_1", "usr_1", "role_missing",
		MutationOperation{OperationKey: "conflict-update", RequestDigest: digestForTest("different")},
		"rlv_missing", RoleUpdatePatch{},
	)
	require.ErrorIs(t, err, ErrRoleMutationConflict)

	applied, err := svc.ApplyRoleOperation(
		ctx, "wsp_1", "usr_1", created.ID,
		MutationOperation{OperationKey: "conflict-apply", RequestDigest: digestForTest("apply")},
		RealizationRequest{AgentID: "agt_1", RoleVersionID: updated.RoleVersionID, Apply: true},
	)
	require.NoError(t, err)
	require.NotEmpty(t, applied.Items)
	_, err = svc.ApplyRoleOperation(
		ctx, "wsp_1", "usr_1", "role_missing",
		MutationOperation{OperationKey: "conflict-apply", RequestDigest: digestForTest("different")},
		RealizationRequest{},
	)
	require.ErrorIs(t, err, ErrRoleMutationConflict)

	deleted, err := svc.DeleteRoleOperation(
		ctx, "wsp_1", "usr_1", created.ID,
		MutationOperation{OperationKey: "conflict-delete", RequestDigest: digestForTest("delete")},
		updated.RoleVersionID,
	)
	require.NoError(t, err)
	require.Equal(t, created.ID, deleted.RoleID)
	_, err = svc.DeleteRoleOperation(
		ctx, "wsp_1", "usr_1", "role_missing",
		MutationOperation{OperationKey: "conflict-delete", RequestDigest: digestForTest("different")},
		"rlv_missing",
	)
	require.ErrorIs(t, err, ErrRoleMutationConflict)

	_, err = svc.CreateRoleOperation(
		ctx, "wsp_1", "usr_1",
		MutationOperation{OperationKey: "conflict-update", RequestDigest: digestForTest("update")},
		Mutation{},
	)
	require.ErrorIs(t, err, ErrRoleMutationConflict)
	_, err = svc.UpdateRoleOperation(
		ctx, "wsp_1", "usr_1", "role_missing",
		MutationOperation{OperationKey: "conflict-create", RequestDigest: digestForTest("create")},
		"rlv_missing", RoleUpdatePatch{},
	)
	require.ErrorIs(t, err, ErrRoleMutationConflict)
	_, err = svc.DeleteRoleOperation(
		ctx, "wsp_1", "usr_1", "role_missing",
		MutationOperation{OperationKey: "conflict-apply", RequestDigest: digestForTest("apply")},
		"rlv_missing",
	)
	require.ErrorIs(t, err, ErrRoleMutationConflict)
	_, err = svc.ApplyRoleOperation(
		ctx, "wsp_1", "usr_1", "role_missing",
		MutationOperation{OperationKey: "conflict-delete", RequestDigest: digestForTest("delete")},
		RealizationRequest{},
	)
	require.ErrorIs(t, err, ErrRoleMutationConflict)
}

func TestRoleOperationCompletedReceiptReplayDoesNotRecheckCurrentRoleReadAuthority(t *testing.T) {
	svc := newRoleServiceForTest(t, Options{})
	ctx := context.Background()
	operation := MutationOperation{OperationKey: "receipt-authority", RequestDigest: digestForTest("authority")}
	input := Mutation{Name: "Private", Definition: "owner-only definition"}

	created, err := svc.CreateRoleOperation(ctx, "wsp_1", "usr_owner", operation, input)
	require.NoError(t, err)
	replayed, err := svc.CreateRoleOperation(ctx, "wsp_1", "usr_outsider", operation, input)
	require.NoError(t, err)
	require.Equal(t, created.ID, replayed.ID)
	require.Equal(t, created.RoleVersionID, replayed.RoleVersionID)
}

func TestRoleOperationReplayUsesReceiptStableIDsAcrossVersionDrift(t *testing.T) {
	svc := newRoleServiceForTest(t, Options{})
	ctx := context.Background()
	createOperation := MutationOperation{OperationKey: "receipt-version-create", RequestDigest: digestForTest("create")}
	created, err := svc.CreateRoleOperation(
		ctx, "wsp_1", "usr_owner", createOperation,
		Mutation{Name: "Versioned", Definition: "definition v1"},
	)
	require.NoError(t, err)

	definition := "definition v2"
	_, err = svc.UpdateRoleOperation(
		ctx, "wsp_1", "usr_owner", created.ID,
		MutationOperation{OperationKey: "receipt-version-update", RequestDigest: digestForTest("update")},
		created.RoleVersionID, RoleUpdatePatch{Definition: &definition},
	)
	require.NoError(t, err)

	replayed, err := svc.CreateRoleOperation(
		ctx, "wsp_1", "usr_owner", createOperation,
		Mutation{Name: "Versioned", Definition: "definition v1"},
	)
	require.NoError(t, err)
	require.Equal(t, created.ID, replayed.ID)
	require.Equal(t, created.RoleVersionID, replayed.RoleVersionID)
	require.Empty(t, replayed.Definition,
		"completed receipt replay must not re-read mutable Role content")
}

func requireReceiptJSON(t *testing.T, svc *Service, operationKey string, expected map[string]string) {
	t.Helper()
	var receipt rolesstore.RoleOperationReceipt
	require.NoError(t, svc.store.DB().Where("workspace_id = ? AND operation_key = ?", "wsp_1", operationKey).First(&receipt).Error)
	t.Logf("%s receipt JSON: %s", receipt.Action, receipt.ResultJSON)
	var actual map[string]string
	require.NoError(t, json.Unmarshal([]byte(receipt.ResultJSON), &actual))
	require.Equal(t, expected, actual)
}

func requireStableApplyReceiptReplay(
	t *testing.T,
	applied RealizationResult,
	replayed RealizationResult,
) {
	t.Helper()
	require.Equal(t, applied.RoleID, replayed.RoleID)
	require.Equal(t, applied.RoleVersionID(), replayed.RoleVersionID())
	require.Equal(t, applied.AgentID, replayed.AgentID)
	require.Equal(t, applied.realizationID, replayed.realizationID)
	require.True(t, replayed.Apply)
	require.Empty(t, replayed.Items,
		"completed receipt replay must return only stable receipt facts")
}
