package grpc

import (
	"context"
	"errors"
	"io"
	"log/slog"
	"testing"
	"time"

	interactionsv1 "github.com/codex-k8s/kodex/proto/gen/go/kodex/interactions/v1"
	"github.com/codex-k8s/kodex/services/internal/interaction-hub/internal/domain/errs"
	interactionservice "github.com/codex-k8s/kodex/services/internal/interaction-hub/internal/domain/service"
	"github.com/codex-k8s/kodex/services/internal/interaction-hub/internal/domain/types/entity"
	"github.com/codex-k8s/kodex/services/internal/interaction-hub/internal/domain/types/enum"
	"github.com/codex-k8s/kodex/services/internal/interaction-hub/internal/domain/types/value"
	"github.com/google/uuid"
	grpcruntime "google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func TestServerRoutesAllStableRPCsToDomainUseCases(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name string
		want enum.Operation
		call func(context.Context, *Server) error
	}{
		{name: "CreateConversationThread", want: enum.OperationCreateConversationThread, call: func(ctx context.Context, s *Server) error {
			_, err := s.CreateConversationThread(ctx, validCreateThreadRequest())
			return err
		}},
		{name: "RecordConversationMessage", want: enum.OperationRecordConversationMessage, call: func(ctx context.Context, s *Server) error {
			_, err := s.RecordConversationMessage(ctx, validRecordMessageRequest(uuid.New()))
			return err
		}},
		{name: "GetConversationThread", want: enum.OperationGetConversationThread, call: func(ctx context.Context, s *Server) error {
			_, err := s.GetConversationThread(ctx, &interactionsv1.GetConversationThreadRequest{ThreadId: uuid.NewString()})
			return err
		}},
		{name: "ListConversationMessages", want: enum.OperationListConversationMessages, call: func(ctx context.Context, s *Server) error {
			_, err := s.ListConversationMessages(ctx, &interactionsv1.ListConversationMessagesRequest{ThreadId: uuid.NewString(), Page: &interactionsv1.PageRequest{PageSize: 10}})
			return err
		}},
		{name: "RequestFeedback", want: enum.OperationRequestFeedback, call: func(ctx context.Context, s *Server) error {
			_, err := s.RequestFeedback(ctx, validRequestFeedbackRequest())
			return err
		}},
		{name: "RequestApproval", want: enum.OperationRequestApproval, call: func(ctx context.Context, s *Server) error {
			_, err := s.RequestApproval(ctx, validRequestApprovalRequest())
			return err
		}},
		{name: "RequestHumanGate", want: enum.OperationRequestHumanGate, call: func(ctx context.Context, s *Server) error {
			_, err := s.RequestHumanGate(ctx, validRequestHumanGateRequest())
			return err
		}},
		{name: "RecordInteractionResponse", want: enum.OperationRecordInteractionResponse, call: func(ctx context.Context, s *Server) error {
			_, err := s.RecordInteractionResponse(ctx, validRecordInteractionResponseRequest(uuid.New()))
			return err
		}},
		{name: "CancelInteractionRequest", want: enum.OperationCancelInteractionRequest, call: func(ctx context.Context, s *Server) error {
			_, err := s.CancelInteractionRequest(ctx, &interactionsv1.CancelInteractionRequestRequest{Meta: commandMetaWithExpected(1), RequestId: uuid.NewString()})
			return err
		}},
		{name: "ExpireInteractionRequests", want: enum.OperationExpireInteractionRequests, call: func(ctx context.Context, s *Server) error {
			_, err := s.ExpireInteractionRequests(ctx, &interactionsv1.ExpireInteractionRequestsRequest{
				Meta:  commandMeta(),
				Scope: &interactionsv1.ScopeRef{Type: interactionsv1.InteractionScopeType_INTERACTION_SCOPE_TYPE_SERVICE, Ref: "agent-manager"},
				Limit: 10,
			})
			return err
		}},
		{name: "GetInteractionRequest", want: enum.OperationGetInteractionRequest, call: func(ctx context.Context, s *Server) error {
			_, err := s.GetInteractionRequest(ctx, &interactionsv1.GetInteractionRequestRequest{RequestId: uuid.NewString()})
			return err
		}},
		{name: "ListInteractionRequests", want: enum.OperationListInteractionRequests, call: func(ctx context.Context, s *Server) error {
			_, err := s.ListInteractionRequests(ctx, &interactionsv1.ListInteractionRequestsRequest{
				Scope: &interactionsv1.ScopeRef{Type: interactionsv1.InteractionScopeType_INTERACTION_SCOPE_TYPE_SERVICE, Ref: "agent-manager"},
				Page:  &interactionsv1.PageRequest{PageSize: 10},
			})
			return err
		}},
		{name: "ListOwnerInboxItems", want: enum.OperationListOwnerInboxItems, call: func(ctx context.Context, s *Server) error {
			_, err := s.ListOwnerInboxItems(ctx, &interactionsv1.ListOwnerInboxItemsRequest{
				Scope: &interactionsv1.ScopeRef{Type: interactionsv1.InteractionScopeType_INTERACTION_SCOPE_TYPE_SERVICE, Ref: "agent-manager"},
				Statuses: []interactionsv1.InteractionRequestStatus{
					interactionsv1.InteractionRequestStatus_INTERACTION_REQUEST_STATUS_WAITING,
				},
				AssigneeRef: &interactionsv1.ActorRef{RefKind: "user", Ref: "approver-1"},
				Page:        &interactionsv1.PageRequest{PageSize: 10},
			})
			return err
		}},
		{name: "GetOwnerInboxItem", want: enum.OperationGetOwnerInboxItem, call: func(ctx context.Context, s *Server) error {
			_, err := s.GetOwnerInboxItem(ctx, &interactionsv1.GetOwnerInboxItemRequest{
				RequestId:   uuid.NewString(),
				Scope:       &interactionsv1.ScopeRef{Type: interactionsv1.InteractionScopeType_INTERACTION_SCOPE_TYPE_SERVICE, Ref: "agent-manager"},
				AssigneeRef: &interactionsv1.ActorRef{RefKind: "user", Ref: "approver-1"},
			})
			return err
		}},
		{name: "RequestNotification", want: enum.OperationRequestNotification, call: func(ctx context.Context, s *Server) error {
			_, err := s.RequestNotification(ctx, validRequestNotificationRequest())
			return err
		}},
		{name: "UpsertSubscription", want: enum.OperationUpsertSubscription, call: func(ctx context.Context, s *Server) error {
			_, err := s.UpsertSubscription(ctx, validUpsertSubscriptionRequest())
			return err
		}},
		{name: "DisableSubscription", want: enum.OperationDisableSubscription, call: func(ctx context.Context, s *Server) error {
			_, err := s.DisableSubscription(ctx, &interactionsv1.DisableSubscriptionRequest{Meta: commandMetaWithExpected(1), SubscriptionId: uuid.NewString()})
			return err
		}},
		{name: "ListSubscriptions", want: enum.OperationListSubscriptions, call: func(ctx context.Context, s *Server) error {
			_, err := s.ListSubscriptions(ctx, &interactionsv1.ListSubscriptionsRequest{
				Scope: &interactionsv1.ScopeRef{Type: interactionsv1.InteractionScopeType_INTERACTION_SCOPE_TYPE_SERVICE, Ref: "agent-manager"},
				Page:  &interactionsv1.PageRequest{PageSize: 10},
			})
			return err
		}},
		{name: "PlanDelivery", want: enum.OperationPlanDelivery, call: func(ctx context.Context, s *Server) error {
			_, err := s.PlanDelivery(ctx, validPlanDeliveryRequest(uuid.New()))
			return err
		}},
		{name: "RecordDeliveryResult", want: enum.OperationRecordDeliveryResult, call: func(ctx context.Context, s *Server) error {
			_, err := s.RecordDeliveryResult(ctx, validRecordDeliveryResultRequest())
			return err
		}},
		{name: "RecordChannelCallback", want: enum.OperationRecordChannelCallback, call: func(ctx context.Context, s *Server) error {
			_, err := s.RecordChannelCallback(ctx, validRecordChannelCallbackRequest())
			return err
		}},
		{name: "GetDeliveryStatus", want: enum.OperationGetDeliveryStatus, call: func(ctx context.Context, s *Server) error {
			_, err := s.GetDeliveryStatus(ctx, validGetDeliveryStatusRequest(uuid.New()))
			return err
		}},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			service := &fakeInteractionService{}
			err := tc.call(context.Background(), NewServer(service))
			if err != nil {
				t.Fatalf("%s() err = %v", tc.name, err)
			}
			if len(service.operations) != 1 || service.operations[0] != tc.want {
				t.Fatalf("operations = %v, want %s", service.operations, tc.want)
			}
		})
	}
}

func TestUnaryErrorInterceptorMapsBacklogToUnimplemented(t *testing.T) {
	t.Parallel()

	interceptor := UnaryErrorInterceptor(slog.New(slog.NewTextHandler(io.Discard, nil)))
	_, err := interceptor(context.Background(), nil, &grpcruntime.UnaryServerInfo{FullMethod: "/kodex.interactions.v1.InteractionHubService/RequestFeedback"}, func(context.Context, any) (any, error) {
		return nil, errs.ErrNotImplemented
	})
	if status.Code(err) != codes.Unimplemented {
		t.Fatalf("code = %s, want Unimplemented", status.Code(err))
	}
}

type fakeInteractionService struct {
	operations []enum.Operation
	err        error
}

func (f *fakeInteractionService) record(operation enum.Operation) error {
	f.operations = append(f.operations, operation)
	if f.err != nil {
		return f.err
	}
	return nil
}

func (f *fakeInteractionService) CreateConversationThread(_ context.Context, input interactionservice.CreateConversationThreadInput) (entity.ConversationThread, error) {
	if err := f.record(enum.OperationCreateConversationThread); err != nil {
		return entity.ConversationThread{}, err
	}
	now := time.Date(2026, 5, 26, 12, 0, 0, 0, time.UTC)
	return entity.ConversationThread{
		ID:              uuid.New(),
		Scope:           input.Scope,
		ThreadKind:      input.ThreadKind,
		PrimaryActorRef: input.PrimaryActorRef,
		SourceKind:      input.SourceKind,
		SourceRef:       input.SourceRef,
		Status:          enum.ConversationThreadStatusOpen,
		CorrelationID:   input.CorrelationID,
		RetentionClass:  input.RetentionClass,
		Version:         1,
		CreatedAt:       now,
		UpdatedAt:       now,
	}, nil
}

func (f *fakeInteractionService) RecordConversationMessage(_ context.Context, input interactionservice.RecordConversationMessageInput) (entity.ConversationMessage, error) {
	if err := f.record(enum.OperationRecordConversationMessage); err != nil {
		return entity.ConversationMessage{}, err
	}
	return entity.ConversationMessage{
		ID:           uuid.New(),
		ThreadID:     input.ThreadID,
		MessageKind:  input.MessageKind,
		AuthorRef:    input.AuthorRef,
		BodySummary:  input.BodySummary,
		BodyObject:   input.BodyObject,
		BodyDigest:   input.BodyDigest,
		Locale:       input.Locale,
		SafeMetadata: input.SafeMetadata,
		CreatedAt:    time.Date(2026, 5, 26, 12, 1, 0, 0, time.UTC),
	}, nil
}

func (f *fakeInteractionService) GetConversationThread(_ context.Context, input interactionservice.GetConversationThreadInput) (entity.ConversationThread, error) {
	if err := f.record(enum.OperationGetConversationThread); err != nil {
		return entity.ConversationThread{}, err
	}
	now := time.Date(2026, 5, 26, 12, 0, 0, 0, time.UTC)
	return entity.ConversationThread{
		ID:             input.ThreadID,
		Scope:          value.ScopeRef{Type: enum.ScopeTypeService, Ref: "interaction-hub"},
		ThreadKind:     enum.ConversationThreadKindUserDialog,
		SourceKind:     enum.ConversationSourceKindService,
		Status:         enum.ConversationThreadStatusOpen,
		CorrelationID:  "trace",
		RetentionClass: "standard",
		Version:        1,
		CreatedAt:      now,
		UpdatedAt:      now,
	}, nil
}

func (f *fakeInteractionService) ListConversationMessages(_ context.Context, input interactionservice.ListConversationMessagesInput) ([]entity.ConversationMessage, value.PageResult, error) {
	if err := f.record(enum.OperationListConversationMessages); err != nil {
		return nil, value.PageResult{}, err
	}
	return []entity.ConversationMessage{{
		ID:          uuid.New(),
		ThreadID:    input.ThreadID,
		MessageKind: enum.ConversationMessageKindAgentText,
		AuthorRef:   "agent:codex",
		CreatedAt:   time.Date(2026, 5, 26, 12, 1, 0, 0, time.UTC),
	}}, value.PageResult{}, nil
}

func (f *fakeInteractionService) RequestFeedback(_ context.Context, input interactionservice.RequestFeedbackInput) (entity.InteractionRequest, error) {
	if err := f.record(enum.OperationRequestFeedback); err != nil {
		return entity.InteractionRequest{}, err
	}
	return fakeRequest(input.Request, enum.InteractionRequestKindFeedback), nil
}

func (f *fakeInteractionService) RequestApproval(_ context.Context, input interactionservice.RequestApprovalInput) (entity.InteractionRequest, error) {
	if err := f.record(enum.OperationRequestApproval); err != nil {
		return entity.InteractionRequest{}, err
	}
	return fakeRequest(input.Request, enum.InteractionRequestKindApproval), nil
}

func (f *fakeInteractionService) RequestHumanGate(_ context.Context, input interactionservice.RequestHumanGateInput) (entity.InteractionRequest, error) {
	if err := f.record(enum.OperationRequestHumanGate); err != nil {
		return entity.InteractionRequest{}, err
	}
	return fakeRequest(input.Request, enum.InteractionRequestKindHumanGate), nil
}

func (f *fakeInteractionService) RecordInteractionResponse(_ context.Context, input interactionservice.RecordInteractionResponseInput) (entity.InteractionRequest, entity.InteractionResponse, error) {
	if err := f.record(enum.OperationRecordInteractionResponse); err != nil {
		return entity.InteractionRequest{}, entity.InteractionResponse{}, err
	}
	now := time.Date(2026, 5, 26, 12, 2, 0, 0, time.UTC)
	request := fakeRequest(validInteractionRequestDraftInput(), enum.InteractionRequestKindApproval)
	request.ID = input.RequestID
	request.Status = enum.InteractionRequestStatusAnswered
	request.Version = 2
	request.UpdatedAt = now
	request.ResolvedAt = &now
	response := entity.InteractionResponse{
		ID:                  uuid.New(),
		RequestID:           input.RequestID,
		ResponseAction:      input.ResponseAction,
		RespondedByActorRef: input.RespondedByActorRef,
		ResponseSummary:     input.ResponseSummary,
		ResponseObject:      input.ResponseObject,
		SourceKind:          input.SourceKind,
		SourceRef:           input.SourceRef,
		OwnerDecisionRef:    input.OwnerDecisionRef,
		CreatedAt:           now,
	}
	return request, response, nil
}

func (f *fakeInteractionService) CancelInteractionRequest(_ context.Context, input interactionservice.CancelInteractionRequestInput) (entity.InteractionRequest, error) {
	if err := f.record(enum.OperationCancelInteractionRequest); err != nil {
		return entity.InteractionRequest{}, err
	}
	request := fakeRequest(validInteractionRequestDraftInput(), enum.InteractionRequestKindApproval)
	request.ID = input.RequestID
	request.Status = enum.InteractionRequestStatusCancelled
	request.Version = 2
	return request, nil
}

func (f *fakeInteractionService) ExpireInteractionRequests(context.Context, interactionservice.ExpireInteractionRequestsInput) (interactionservice.ExpireInteractionRequestsResult, error) {
	if err := f.record(enum.OperationExpireInteractionRequests); err != nil {
		return interactionservice.ExpireInteractionRequestsResult{}, err
	}
	return interactionservice.ExpireInteractionRequestsResult{ExpiredRequestIDs: []uuid.UUID{uuid.New()}}, nil
}

func (f *fakeInteractionService) GetInteractionRequest(_ context.Context, input interactionservice.GetInteractionRequestInput) (entity.InteractionRequest, error) {
	if err := f.record(enum.OperationGetInteractionRequest); err != nil {
		return entity.InteractionRequest{}, err
	}
	request := fakeRequest(validInteractionRequestDraftInput(), enum.InteractionRequestKindApproval)
	request.ID = input.RequestID
	return request, nil
}

func (f *fakeInteractionService) ListInteractionRequests(context.Context, interactionservice.ListInteractionRequestsInput) ([]entity.InteractionRequest, value.PageResult, error) {
	if err := f.record(enum.OperationListInteractionRequests); err != nil {
		return nil, value.PageResult{}, err
	}
	return []entity.InteractionRequest{fakeRequest(validInteractionRequestDraftInput(), enum.InteractionRequestKindApproval)}, value.PageResult{}, nil
}

func (f *fakeInteractionService) ListOwnerInboxItems(context.Context, interactionservice.ListOwnerInboxItemsInput) ([]entity.OwnerInboxItem, value.PageResult, error) {
	if err := f.record(enum.OperationListOwnerInboxItems); err != nil {
		return nil, value.PageResult{}, err
	}
	request := fakeRequest(validInteractionRequestDraftInput(), enum.InteractionRequestKindApproval)
	return []entity.OwnerInboxItem{{
		Request: request,
		Title:   "safe title",
		Summary: "safe summary",
		DeliverySummary: entity.OwnerInboxDeliverySummary{
			AttemptCount:     1,
			LatestDeliveryID: "delivery-1",
			LatestStatus:     enum.DeliveryAttemptStatusDelivered,
		},
	}}, value.PageResult{}, nil
}

func (f *fakeInteractionService) GetOwnerInboxItem(_ context.Context, input interactionservice.GetOwnerInboxItemInput) (entity.OwnerInboxItem, error) {
	if err := f.record(enum.OperationGetOwnerInboxItem); err != nil {
		return entity.OwnerInboxItem{}, err
	}
	request := fakeRequest(validInteractionRequestDraftInput(), enum.InteractionRequestKindHumanGate)
	request.ID = input.RequestID
	return entity.OwnerInboxItem{
		Request: request,
		Title:   "safe title",
		Summary: "safe summary",
		DeliverySummary: entity.OwnerInboxDeliverySummary{
			AttemptCount:     1,
			LatestDeliveryID: "delivery-1",
			LatestStatus:     enum.DeliveryAttemptStatusDelivered,
		},
	}, nil
}

func (f *fakeInteractionService) RequestNotification(_ context.Context, input interactionservice.RequestNotificationInput) (entity.Notification, error) {
	if err := f.record(enum.OperationRequestNotification); err != nil {
		return entity.Notification{}, err
	}
	now := time.Date(2026, 5, 26, 12, 3, 0, 0, time.UTC)
	return entity.Notification{
		ID:                    uuid.New(),
		Scope:                 input.Scope,
		NotificationKind:      input.NotificationKind,
		RequestID:             optionalUUID(input.RequestID),
		SubscriptionID:        optionalUUID(input.SubscriptionID),
		RecipientRefs:         input.RecipientRefs,
		MessageTemplateRef:    input.MessageTemplateRef,
		MessageTitle:          input.MessageTitle,
		MessageSummary:        input.MessageSummary,
		BodyPreview:           input.BodyPreview,
		Priority:              input.Priority,
		Status:                enum.NotificationStatusCreated,
		SourceOwner:           input.SourceOwner,
		Ingress:               input.Ingress,
		ContextRefs:           input.ContextRefs,
		ChannelHintRefs:       input.ChannelHintRefs,
		NotificationPolicyRef: input.NotificationPolicyRef,
		CreatedAt:             now,
		UpdatedAt:             now,
		ExpiresAt:             input.ExpiresAt,
	}, nil
}

func (f *fakeInteractionService) UpsertSubscription(_ context.Context, input interactionservice.UpsertSubscriptionInput) (entity.Subscription, error) {
	if err := f.record(enum.OperationUpsertSubscription); err != nil {
		return entity.Subscription{}, err
	}
	now := time.Date(2026, 5, 26, 12, 3, 0, 0, time.UTC)
	id := input.SubscriptionID
	if id == uuid.Nil {
		id = uuid.New()
	}
	return entity.Subscription{
		ID:                      id,
		Scope:                   input.Scope,
		SubscriberRef:           input.SubscriberRef,
		EventFilterJSON:         input.EventFilterJSON,
		DeliveryPreferencesJSON: input.DeliveryPreferencesJSON,
		Status:                  input.Status,
		Version:                 1,
		SourceOwner:             input.SourceOwner,
		ChannelHintRefs:         input.ChannelHintRefs,
		SubscriptionPolicyRef:   input.SubscriptionPolicyRef,
		CreatedAt:               now,
		UpdatedAt:               now,
	}, nil
}

func (f *fakeInteractionService) DisableSubscription(_ context.Context, input interactionservice.DisableSubscriptionInput) (entity.Subscription, error) {
	if err := f.record(enum.OperationDisableSubscription); err != nil {
		return entity.Subscription{}, err
	}
	now := time.Date(2026, 5, 26, 12, 4, 0, 0, time.UTC)
	return entity.Subscription{
		ID:            input.SubscriptionID,
		Scope:         value.ScopeRef{Type: enum.ScopeTypeService, Ref: "agent-manager"},
		SubscriberRef: value.ActorRef{Kind: "user", Ref: "owner-1"},
		Status:        enum.SubscriptionStatusDisabled,
		Version:       2,
		SourceOwner:   value.SourceOwnerRef{Kind: enum.SourceOwnerKindAgentManager, Ref: "run:123"},
		CreatedAt:     now.Add(-time.Minute),
		UpdatedAt:     now,
	}, nil
}

func (f *fakeInteractionService) ListSubscriptions(context.Context, interactionservice.ListSubscriptionsInput) ([]entity.Subscription, value.PageResult, error) {
	if err := f.record(enum.OperationListSubscriptions); err != nil {
		return nil, value.PageResult{}, err
	}
	now := time.Date(2026, 5, 26, 12, 3, 0, 0, time.UTC)
	return []entity.Subscription{{
		ID:            uuid.New(),
		Scope:         value.ScopeRef{Type: enum.ScopeTypeService, Ref: "agent-manager"},
		SubscriberRef: value.ActorRef{Kind: "user", Ref: "owner-1"},
		Status:        enum.SubscriptionStatusActive,
		Version:       1,
		SourceOwner:   value.SourceOwnerRef{Kind: enum.SourceOwnerKindAgentManager, Ref: "run:123"},
		CreatedAt:     now,
		UpdatedAt:     now,
	}}, value.PageResult{}, nil
}

func (f *fakeInteractionService) PlanDelivery(_ context.Context, input interactionservice.PlanDeliveryInput) (entity.DeliveryAttempt, error) {
	if err := f.record(enum.OperationPlanDelivery); err != nil {
		return entity.DeliveryAttempt{}, err
	}
	now := time.Date(2026, 5, 26, 12, 5, 0, 0, time.UTC)
	id := uuid.New()
	return entity.DeliveryAttempt{
		ID:            id,
		Target:        input.Target,
		RouteID:       input.RouteID,
		DeliveryID:    id.String(),
		DeliveryKind:  enum.DeliveryKindApproval,
		Status:        enum.DeliveryAttemptStatusQueued,
		AttemptNumber: 1,
		PayloadDigest: "sha256:digest",
		CreatedAt:     now,
		UpdatedAt:     now,
	}, nil
}

func (f *fakeInteractionService) RecordDeliveryResult(_ context.Context, input interactionservice.RecordDeliveryResultInput) (entity.DeliveryAttempt, error) {
	if err := f.record(enum.OperationRecordDeliveryResult); err != nil {
		return entity.DeliveryAttempt{}, err
	}
	now := time.Date(2026, 5, 26, 12, 6, 0, 0, time.UTC)
	id := uuid.New()
	return entity.DeliveryAttempt{
		ID:                id,
		Target:            value.DeliveryTarget{Kind: value.DeliveryTargetKindRequest, ID: uuid.New()},
		RouteID:           uuid.New(),
		DeliveryID:        input.Result.DeliveryID,
		DeliveryKind:      enum.DeliveryKindApproval,
		Status:            enum.DeliveryAttemptStatusAccepted,
		ChannelMessageRef: input.Result.ChannelMessageRef,
		AttemptNumber:     1,
		PayloadDigest:     "sha256:digest",
		CreatedAt:         now.Add(-time.Minute),
		UpdatedAt:         now,
		SentAt:            &now,
	}, nil
}

func (f *fakeInteractionService) RecordChannelCallback(_ context.Context, input interactionservice.RecordChannelCallbackInput) (interactionservice.ChannelCallbackResult, error) {
	if err := f.record(enum.OperationRecordChannelCallback); err != nil {
		return interactionservice.ChannelCallbackResult{}, err
	}
	now := time.Date(2026, 5, 26, 12, 7, 0, 0, time.UTC)
	return interactionservice.ChannelCallbackResult{Callback: entity.ChannelCallback{
		ID:               uuid.New(),
		CallbackID:       input.Callback.CallbackID,
		DeliveryID:       input.Callback.DeliveryID,
		ActorRef:         input.Callback.ActorRef,
		Action:           input.Callback.Action,
		CallbackSummary:  input.Callback.AnswerSummary,
		SignatureStatus:  input.Callback.SignatureStatus,
		ProcessingStatus: enum.CallbackProcessingStatusAccepted,
		ReceivedAt:       input.Callback.ReceivedAt,
		CreatedAt:        now,
		CallbackRouteRef: "callback-route:interaction-channel",
		GatewayRef:       input.Callback.GatewayRef,
		CorrelationID:    input.Callback.CorrelationID,
	}}, nil
}

func (f *fakeInteractionService) GetDeliveryStatus(_ context.Context, input interactionservice.GetDeliveryStatusInput) (interactionservice.DeliveryStatusResult, error) {
	if err := f.record(enum.OperationGetDeliveryStatus); err != nil {
		return interactionservice.DeliveryStatusResult{}, err
	}
	now := time.Date(2026, 5, 26, 12, 7, 0, 0, time.UTC)
	request := fakeRequest(validInteractionRequestDraftInput(), enum.InteractionRequestKindApproval)
	request.ID = input.Target.ID
	return interactionservice.DeliveryStatusResult{
		Request: &request,
		DeliveryAttempts: []entity.DeliveryAttempt{{
			ID:            uuid.New(),
			Target:        input.Target,
			RouteID:       uuid.New(),
			DeliveryID:    "delivery:1",
			DeliveryKind:  enum.DeliveryKindApproval,
			Status:        enum.DeliveryAttemptStatusAccepted,
			AttemptNumber: 1,
			PayloadDigest: "sha256:digest",
			CreatedAt:     now.Add(-time.Minute),
			UpdatedAt:     now,
		}},
	}, nil
}

var _ interactionService = (*fakeInteractionService)(nil)

func TestServerReturnsDomainError(t *testing.T) {
	t.Parallel()

	service := &fakeInteractionService{err: errs.ErrNotImplemented}
	_, err := NewServer(service).RequestFeedback(context.Background(), validRequestFeedbackRequest())
	if !errors.Is(err, errs.ErrNotImplemented) {
		t.Fatalf("RequestFeedback() err = %v, want ErrNotImplemented", err)
	}
}

func validCreateThreadRequest() *interactionsv1.CreateConversationThreadRequest {
	return &interactionsv1.CreateConversationThreadRequest{
		Meta: commandMeta(),
		Scope: &interactionsv1.ScopeRef{
			Type: interactionsv1.InteractionScopeType_INTERACTION_SCOPE_TYPE_SERVICE,
			Ref:  "agent-manager",
		},
		ThreadKind:      interactionsv1.ConversationThreadKind_CONVERSATION_THREAD_KIND_USER_DIALOG,
		PrimaryActorRef: ptr("service:agent-manager"),
		SourceKind:      interactionsv1.ConversationSourceKind_CONVERSATION_SOURCE_KIND_SERVICE,
		SourceRef:       ptr("run:123"),
		CorrelationId:   "trace-123",
		RetentionClass:  "standard",
	}
}

func validRecordMessageRequest(threadID uuid.UUID) *interactionsv1.RecordConversationMessageRequest {
	size := int64(128)
	return &interactionsv1.RecordConversationMessageRequest{
		Meta:        commandMeta(),
		ThreadId:    threadID.String(),
		MessageKind: interactionsv1.ConversationMessageKind_CONVERSATION_MESSAGE_KIND_AGENT_TEXT,
		AuthorRef:   "agent:codex",
		BodySummary: ptr("safe summary"),
		BodyObject: &interactionsv1.ObjectRef{
			ObjectUri:       "s3://kodex-interactions/messages/1",
			ObjectDigest:    "sha256:abc",
			ObjectSizeBytes: &size,
		},
		BodyDigest:   ptr("sha256:def"),
		Locale:       ptr("ru"),
		SafeMetadata: map[string]string{"surface": "mcp"},
	}
}

func validRequestFeedbackRequest() *interactionsv1.RequestFeedbackRequest {
	return &interactionsv1.RequestFeedbackRequest{Meta: commandMeta(), Request: validInteractionRequestDraft()}
}

func validRequestApprovalRequest() *interactionsv1.RequestApprovalRequest {
	return &interactionsv1.RequestApprovalRequest{Meta: commandMeta(), Request: validInteractionRequestDraft()}
}

func validRequestHumanGateRequest() *interactionsv1.RequestHumanGateRequest {
	return &interactionsv1.RequestHumanGateRequest{Meta: commandMeta(), Request: validInteractionRequestDraft()}
}

func validRecordInteractionResponseRequest(requestID uuid.UUID) *interactionsv1.RecordInteractionResponseRequest {
	return &interactionsv1.RecordInteractionResponseRequest{
		Meta:                commandMetaWithExpected(1),
		RequestId:           requestID.String(),
		ResponseAction:      interactionsv1.InteractionResponseAction_INTERACTION_RESPONSE_ACTION_APPROVE,
		RespondedByActorRef: "user:approver-1",
		ResponseSummary:     ptr("approved"),
		SourceKind:          interactionsv1.InteractionResponseSourceKind_INTERACTION_RESPONSE_SOURCE_KIND_MCP,
		SourceRef:           ptr("mcp:command-1"),
		OwnerDecisionRef:    ptr("decision:1"),
	}
}

func validRequestNotificationRequest() *interactionsv1.RequestNotificationRequest {
	expiresAt := time.Date(2026, 5, 26, 13, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)
	return &interactionsv1.RequestNotificationRequest{
		Meta:             commandMeta(),
		Scope:            &interactionsv1.ScopeRef{Type: interactionsv1.InteractionScopeType_INTERACTION_SCOPE_TYPE_SERVICE, Ref: "agent-manager"},
		NotificationKind: interactionsv1.NotificationKind_NOTIFICATION_KIND_ATTENTION,
		RecipientRefs: []*interactionsv1.ActorRef{
			{RefKind: "user", Ref: "owner-1"},
		},
		MessageTemplateRef: "interaction.notification.attention",
		MessageTitle:       ptr("Safe title"),
		MessageSummary:     "Safe summary",
		BodyPreview:        ptr("Safe preview"),
		Priority:           interactionsv1.NotificationPriority_NOTIFICATION_PRIORITY_HIGH,
		ExpiresAt:          &expiresAt,
		SourceOwner:        &interactionsv1.SourceOwnerRef{Kind: interactionsv1.SourceOwnerKind_SOURCE_OWNER_KIND_AGENT_MANAGER, Ref: ptr("run:123")},
		Ingress:            &interactionsv1.IngressRef{Kind: interactionsv1.IngressKind_INGRESS_KIND_DIRECT_GRPC, Ref: ptr("grpc:notify-1")},
		ContextRefs: []*interactionsv1.ExternalRef{
			{RefKind: "agent_run", Ref: "run:123"},
		},
		ChannelHintRefs: []*interactionsv1.ExternalRef{
			{RefKind: "surface", Ref: "web_console"},
		},
		NotificationPolicyRef: ptr("policy:notify-standard"),
	}
}

func validUpsertSubscriptionRequest() *interactionsv1.UpsertSubscriptionRequest {
	return &interactionsv1.UpsertSubscriptionRequest{
		Meta:                    commandMeta(),
		Scope:                   &interactionsv1.ScopeRef{Type: interactionsv1.InteractionScopeType_INTERACTION_SCOPE_TYPE_SERVICE, Ref: "agent-manager"},
		SubscriberRef:           &interactionsv1.ActorRef{RefKind: "user", Ref: "owner-1"},
		EventFilterJson:         `{"event_kind":["run_waiting"],"severity":["high"]}`,
		DeliveryPreferencesJson: `{"surfaces":["web_console"],"fallback_policy_ref":"policy:fallback"}`,
		Status:                  interactionsv1.SubscriptionStatus_SUBSCRIPTION_STATUS_ACTIVE,
		SourceOwner:             &interactionsv1.SourceOwnerRef{Kind: interactionsv1.SourceOwnerKind_SOURCE_OWNER_KIND_AGENT_MANAGER, Ref: ptr("run:123")},
		ChannelHintRefs: []*interactionsv1.ExternalRef{
			{RefKind: "surface", Ref: "web_console"},
		},
		SubscriptionPolicyRef: ptr("policy:ops-notifications"),
	}
}

func validPlanDeliveryRequest(requestID uuid.UUID) *interactionsv1.PlanDeliveryRequest {
	return &interactionsv1.PlanDeliveryRequest{
		Meta: commandMeta(),
		Target: &interactionsv1.DeliveryTarget{
			Target: &interactionsv1.DeliveryTarget_RequestId{RequestId: requestID.String()},
		},
		RouteId:       ptr(uuid.NewString()),
		CorrelationId: ptr("trace-delivery"),
	}
}

func validRecordDeliveryResultRequest() *interactionsv1.RecordDeliveryResultRequest {
	occurredAt := time.Date(2026, 5, 26, 12, 6, 0, 0, time.UTC).Format(time.RFC3339Nano)
	return &interactionsv1.RecordDeliveryResultRequest{
		Meta: commandMeta(),
		Result: &interactionsv1.ChannelDeliveryResult{
			ContractVersion:   "interaction.channel.v1",
			DeliveryId:        "delivery-1",
			ResultStatus:      interactionsv1.ChannelDeliveryResultStatus_CHANNEL_DELIVERY_RESULT_STATUS_ACCEPTED,
			ChannelMessageRef: ptr("channel:message-1"),
			OccurredAt:        occurredAt,
		},
	}
}

func validRecordChannelCallbackRequest() *interactionsv1.RecordChannelCallbackRequest {
	receivedAt := time.Date(2026, 5, 26, 12, 7, 0, 0, time.UTC).Format(time.RFC3339Nano)
	return &interactionsv1.RecordChannelCallbackRequest{
		Meta: commandMeta(),
		Callback: &interactionsv1.ChannelCallbackEnvelope{
			ContractVersion: "interaction.channel.v1",
			CallbackId:      "callback-1",
			DeliveryId:      ptr("delivery-1"),
			RequestRef:      ptr(uuid.NewString()),
			ActorRef:        ptr("user:approver-1"),
			Action:          "approve",
			AnswerSummary:   ptr("safe callback summary"),
			SignatureStatus: interactionsv1.CallbackSignatureStatus_CALLBACK_SIGNATURE_STATUS_VERIFIED,
			GatewayRef:      ptr("gateway:request-1"),
			ReceivedAt:      receivedAt,
			CorrelationId:   "trace-callback",
		},
	}
}

func validGetDeliveryStatusRequest(requestID uuid.UUID) *interactionsv1.GetDeliveryStatusRequest {
	return &interactionsv1.GetDeliveryStatusRequest{
		Target: &interactionsv1.DeliveryTarget{
			Target: &interactionsv1.DeliveryTarget_RequestId{RequestId: requestID.String()},
		},
	}
}

func validInteractionRequestDraft() *interactionsv1.InteractionRequestDraft {
	deadline := time.Date(2026, 5, 26, 13, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)
	policy := "policy:standard"
	return &interactionsv1.InteractionRequestDraft{
		Scope: &interactionsv1.ScopeRef{
			Type: interactionsv1.InteractionScopeType_INTERACTION_SCOPE_TYPE_SERVICE,
			Ref:  "agent-manager",
		},
		SourceOwner: &interactionsv1.SourceOwnerRef{
			Kind: interactionsv1.SourceOwnerKind_SOURCE_OWNER_KIND_AGENT_MANAGER,
			Ref:  ptr("run:123"),
		},
		Ingress: &interactionsv1.IngressRef{
			Kind: interactionsv1.IngressKind_INGRESS_KIND_DIRECT_GRPC,
			Ref:  ptr("grpc:command-1"),
		},
		DecisionOwner: &interactionsv1.DecisionOwnerRef{
			OwnerKind:       interactionsv1.DecisionOwnerKind_DECISION_OWNER_KIND_GOVERNANCE_MANAGER,
			OwnerRequestRef: "gate:req-1",
		},
		TargetRefs: []*interactionsv1.ActorRef{
			{RefKind: "user", Ref: "approver-1"},
		},
		ContextRefs: []*interactionsv1.ExternalRef{
			{RefKind: "agent_run", Ref: "run:123"},
		},
		PromptSummary: "safe prompt",
		AllowedActions: []*interactionsv1.InteractionAction{
			{ActionKey: "approve", LabelTemplateRef: ptr("interaction.actions.approve"), IsTerminal: true},
		},
		RiskClass:         interactionsv1.InteractionRiskClass_INTERACTION_RISK_CLASS_HIGH,
		DeadlineAt:        &deadline,
		ReminderPolicyRef: &policy,
	}
}

func validInteractionRequestDraftInput() interactionservice.InteractionRequestDraftInput {
	deadline := time.Date(2026, 5, 26, 13, 0, 0, 0, time.UTC)
	return interactionservice.InteractionRequestDraftInput{
		Scope:         value.ScopeRef{Type: enum.ScopeTypeService, Ref: "agent-manager"},
		SourceOwner:   value.SourceOwnerRef{Kind: enum.SourceOwnerKindAgentManager, Ref: "run:123"},
		Ingress:       value.IngressRef{Kind: enum.IngressKindDirectGRPC, Ref: "grpc:command-1"},
		DecisionOwner: value.DecisionOwnerRef{Kind: enum.DecisionOwnerKindGovernanceManager, OwnerRequestRef: "gate:req-1"},
		TargetRefs: []value.ActorRef{
			{Kind: "user", Ref: "approver-1"},
		},
		ContextRefs: []value.ExternalRef{
			{Kind: "agent_run", Ref: "run:123"},
		},
		PromptSummary: "safe prompt",
		AllowedActions: []value.InteractionAction{
			{ActionKey: "approve", LabelTemplateRef: "interaction.actions.approve", Terminal: true},
		},
		RiskClass:         enum.InteractionRiskClassHigh,
		DeadlineAt:        &deadline,
		ReminderPolicyRef: "policy:standard",
	}
}

func optionalUUID(input uuid.UUID) *uuid.UUID {
	if input == uuid.Nil {
		return nil
	}
	value := input
	return &value
}

func fakeRequest(input interactionservice.InteractionRequestDraftInput, kind enum.InteractionRequestKind) entity.InteractionRequest {
	now := time.Date(2026, 5, 26, 12, 0, 0, 0, time.UTC)
	return entity.InteractionRequest{
		ID:                uuid.New(),
		RequestKind:       kind,
		Scope:             input.Scope,
		SourceOwner:       input.SourceOwner,
		Ingress:           input.Ingress,
		DecisionOwner:     input.DecisionOwner,
		TargetRefs:        input.TargetRefs,
		ContextRefs:       input.ContextRefs,
		PromptSummary:     input.PromptSummary,
		PromptObject:      input.PromptObject,
		AllowedActions:    input.AllowedActions,
		RiskClass:         input.RiskClass,
		Status:            enum.InteractionRequestStatusWaiting,
		DeadlineAt:        input.DeadlineAt,
		ReminderPolicyRef: input.ReminderPolicyRef,
		Version:           1,
		CreatedAt:         now,
		UpdatedAt:         now,
	}
}

func commandMeta() *interactionsv1.CommandMeta {
	commandID := uuid.NewString()
	return &interactionsv1.CommandMeta{
		CommandId: &commandID,
		Actor:     &interactionsv1.Actor{Type: "service", Id: "interaction-test"},
		Reason:    "test",
		RequestId: "request-1",
	}
}

func commandMetaWithExpected(version int64) *interactionsv1.CommandMeta {
	meta := commandMeta()
	meta.ExpectedVersion = &version
	return meta
}

func ptr(value string) *string {
	return &value
}
