package orchestrator

import (
	"context"
	"strings"

	"github.com/tech/sendico/payments/orchestrator/storage"
	"github.com/tech/sendico/payments/orchestrator/storage/model"
	"github.com/tech/sendico/pkg/api/routers"
	"github.com/tech/sendico/pkg/api/routers/gsresponse"
	clockpkg "github.com/tech/sendico/pkg/clock"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/mservice"
	paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
	orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"google.golang.org/grpc"
)

type serviceError string

func (e serviceError) Error() string {
	return string(e)
}

const (
	defaultFeeQuoteTTLMillis int64 = 120000
	defaultOracleTTLMillis   int64 = 60000
)

var (
	errStorageUnavailable = serviceError("payments.orchestrator: storage not initialised")
)

// Service orchestrates payments across ledger, billing, FX, and chain domains.
type Service struct {
	logger  mlogger.Logger
	storage storage.Repository
	clock   clockpkg.Clock

	fees    feesDependency
	ledger  ledgerDependency
	gateway gatewayDependency
	oracle  oracleDependency

	orchestratorv1.UnimplementedPaymentOrchestratorServer
}

// NewService constructs a payment orchestrator service.
func NewService(logger mlogger.Logger, repo storage.Repository, opts ...Option) *Service {
	svc := &Service{
		logger:  logger.Named("payment_orchestrator"),
		storage: repo,
		clock:   clockpkg.NewSystem(),
	}

	initMetrics()

	for _, opt := range opts {
		if opt != nil {
			opt(svc)
		}
	}

	if svc.clock == nil {
		svc.clock = clockpkg.NewSystem()
	}

	return svc
}

// Register attaches the service to the supplied gRPC router.
func (s *Service) Register(router routers.GRPC) error {
	return router.Register(func(reg grpc.ServiceRegistrar) {
		orchestratorv1.RegisterPaymentOrchestratorServer(reg, s)
	})
}

// QuotePayment aggregates downstream quotes.
func (s *Service) QuotePayment(ctx context.Context, req *orchestratorv1.QuotePaymentRequest) (*orchestratorv1.QuotePaymentResponse, error) {
	return executeUnary(ctx, s, "QuotePayment", s.quotePaymentHandler, req)
}

// InitiatePayment captures a payment intent and reserves funds orchestration.
func (s *Service) InitiatePayment(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error) {
	return executeUnary(ctx, s, "InitiatePayment", s.initiatePaymentHandler, req)
}

// CancelPayment attempts to cancel an in-flight payment.
func (s *Service) CancelPayment(ctx context.Context, req *orchestratorv1.CancelPaymentRequest) (*orchestratorv1.CancelPaymentResponse, error) {
	return executeUnary(ctx, s, "CancelPayment", s.cancelPaymentHandler, req)
}

// GetPayment returns a stored payment record.
func (s *Service) GetPayment(ctx context.Context, req *orchestratorv1.GetPaymentRequest) (*orchestratorv1.GetPaymentResponse, error) {
	return executeUnary(ctx, s, "GetPayment", s.getPaymentHandler, req)
}

// ListPayments lists stored payment records.
func (s *Service) ListPayments(ctx context.Context, req *orchestratorv1.ListPaymentsRequest) (*orchestratorv1.ListPaymentsResponse, error) {
	return executeUnary(ctx, s, "ListPayments", s.listPaymentsHandler, req)
}

// InitiateConversion orchestrates standalone FX conversions.
func (s *Service) InitiateConversion(ctx context.Context, req *orchestratorv1.InitiateConversionRequest) (*orchestratorv1.InitiateConversionResponse, error) {
	return executeUnary(ctx, s, "InitiateConversion", s.initiateConversionHandler, req)
}

// ProcessTransferUpdate reconciles chain events back into payment state.
func (s *Service) ProcessTransferUpdate(ctx context.Context, req *orchestratorv1.ProcessTransferUpdateRequest) (*orchestratorv1.ProcessTransferUpdateResponse, error) {
	return executeUnary(ctx, s, "ProcessTransferUpdate", s.processTransferUpdateHandler, req)
}

// ProcessDepositObserved reconciles deposit events to ledger.
func (s *Service) ProcessDepositObserved(ctx context.Context, req *orchestratorv1.ProcessDepositObservedRequest) (*orchestratorv1.ProcessDepositObservedResponse, error) {
	return executeUnary(ctx, s, "ProcessDepositObserved", s.processDepositObservedHandler, req)
}

func (s *Service) quotePaymentHandler(ctx context.Context, req *orchestratorv1.QuotePaymentRequest) gsresponse.Responder[orchestratorv1.QuotePaymentResponse] {
	if err := s.ensureRepository(ctx); err != nil {
		return gsresponse.Unavailable[orchestratorv1.QuotePaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	if req == nil {
		return gsresponse.InvalidArgument[orchestratorv1.QuotePaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("nil request"))
	}
	meta := req.GetMeta()
	if meta == nil {
		return gsresponse.InvalidArgument[orchestratorv1.QuotePaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("meta is required"))
	}
	orgRef := strings.TrimSpace(meta.GetOrganizationRef())
	if orgRef == "" {
		return gsresponse.InvalidArgument[orchestratorv1.QuotePaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("organization_ref is required"))
	}
	intent := req.GetIntent()
	if intent == nil {
		return gsresponse.InvalidArgument[orchestratorv1.QuotePaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("intent is required"))
	}
	if intent.GetAmount() == nil {
		return gsresponse.InvalidArgument[orchestratorv1.QuotePaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("intent.amount is required"))
	}

	quote, err := s.buildPaymentQuote(ctx, orgRef, req)
	if err != nil {
		return gsresponse.Auto[orchestratorv1.QuotePaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
	}

	return gsresponse.Success(&orchestratorv1.QuotePaymentResponse{Quote: quote})
}

func (s *Service) initiatePaymentHandler(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) gsresponse.Responder[orchestratorv1.InitiatePaymentResponse] {
	if err := s.ensureRepository(ctx); err != nil {
		return gsresponse.Unavailable[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	if req == nil {
		return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("nil request"))
	}
	meta := req.GetMeta()
	if meta == nil {
		return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("meta is required"))
	}
	orgRef := strings.TrimSpace(meta.GetOrganizationRef())
	if orgRef == "" {
		return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("organization_ref is required"))
	}
	orgObjectID, parseErr := primitive.ObjectIDFromHex(orgRef)
	if parseErr != nil {
		return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("organization_ref must be a valid objectID"))
	}
	intent := req.GetIntent()
	if intent == nil {
		return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("intent is required"))
	}
	if intent.GetAmount() == nil {
		return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("intent.amount is required"))
	}
	idempotencyKey := strings.TrimSpace(req.GetIdempotencyKey())
	if idempotencyKey == "" {
		return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("idempotency_key is required"))
	}

	store := s.storage.Payments()
	if store == nil {
		return gsresponse.Unavailable[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, errStorageUnavailable)
	}

	existing, err := store.GetByIdempotencyKey(ctx, orgObjectID, idempotencyKey)
	if err == nil && existing != nil {
		return gsresponse.Success(&orchestratorv1.InitiatePaymentResponse{
			Payment: toProtoPayment(existing),
		})
	}
	if err != nil && err != storage.ErrPaymentNotFound {
		return gsresponse.Auto[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
	}

	quote := req.GetFeeQuoteToken()
	var quoteSnapshot *orchestratorv1.PaymentQuote
	if quote == "" {
		quoteSnapshot, err = s.buildPaymentQuote(ctx, orgRef, &orchestratorv1.QuotePaymentRequest{
			Meta:           req.GetMeta(),
			IdempotencyKey: req.GetIdempotencyKey(),
			Intent:         req.GetIntent(),
			PreviewOnly:    false,
		})
		if err != nil {
			return gsresponse.Auto[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
		}
	} else {
		quoteSnapshot = &orchestratorv1.PaymentQuote{FeeQuoteToken: quote}
	}

	entity := &model.Payment{}
	entity.SetID(primitive.NewObjectID())
	entity.SetOrganizationRef(orgObjectID)
	entity.PaymentRef = entity.GetID().Hex()
	entity.IdempotencyKey = idempotencyKey
	entity.State = model.PaymentStateAccepted
	entity.Intent = intentFromProto(intent)
	entity.Metadata = cloneMetadata(req.GetMetadata())
	entity.LastQuote = quoteSnapshotToModel(quoteSnapshot)
	entity.Normalize()

	if err = store.Create(ctx, entity); err != nil {
		if err == storage.ErrDuplicatePayment {
			return gsresponse.Auto[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.DataConflict("payment already exists"))
		}
		return gsresponse.Auto[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
	}

	if quoteSnapshot == nil {
		quoteSnapshot = &orchestratorv1.PaymentQuote{}
	}

	if err := s.executePayment(ctx, store, entity, quoteSnapshot); err != nil {
		return gsresponse.Auto[orchestratorv1.InitiatePaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
	}

	return gsresponse.Success(&orchestratorv1.InitiatePaymentResponse{
		Payment: toProtoPayment(entity),
	})
}

func (s *Service) cancelPaymentHandler(ctx context.Context, req *orchestratorv1.CancelPaymentRequest) gsresponse.Responder[orchestratorv1.CancelPaymentResponse] {
	if err := s.ensureRepository(ctx); err != nil {
		return gsresponse.Unavailable[orchestratorv1.CancelPaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	if req == nil {
		return gsresponse.InvalidArgument[orchestratorv1.CancelPaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("nil request"))
	}
	paymentRef := strings.TrimSpace(req.GetPaymentRef())
	if paymentRef == "" {
		return gsresponse.InvalidArgument[orchestratorv1.CancelPaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("payment_ref is required"))
	}
	store := s.storage.Payments()
	if store == nil {
		return gsresponse.Unavailable[orchestratorv1.CancelPaymentResponse](s.logger, mservice.PaymentOrchestrator, errStorageUnavailable)
	}
	payment, err := store.GetByPaymentRef(ctx, paymentRef)
	if err != nil {
		if err == storage.ErrPaymentNotFound {
			return gsresponse.NotFound[orchestratorv1.CancelPaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
		}
		return gsresponse.Auto[orchestratorv1.CancelPaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	if payment.State != model.PaymentStateAccepted {
		reason := merrors.InvalidArgument("payment cannot be cancelled in current state")
		return gsresponse.FailedPrecondition[orchestratorv1.CancelPaymentResponse](s.logger, mservice.PaymentOrchestrator, "payment_not_cancellable", reason)
	}
	payment.State = model.PaymentStateCancelled
	payment.FailureCode = model.PaymentFailureCodePolicy
	payment.FailureReason = strings.TrimSpace(req.GetReason())
	if err := store.Update(ctx, payment); err != nil {
		return gsresponse.Auto[orchestratorv1.CancelPaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	return gsresponse.Success(&orchestratorv1.CancelPaymentResponse{Payment: toProtoPayment(payment)})
}

func (s *Service) getPaymentHandler(ctx context.Context, req *orchestratorv1.GetPaymentRequest) gsresponse.Responder[orchestratorv1.GetPaymentResponse] {
	if err := s.ensureRepository(ctx); err != nil {
		return gsresponse.Unavailable[orchestratorv1.GetPaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	if req == nil {
		return gsresponse.InvalidArgument[orchestratorv1.GetPaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("nil request"))
	}
	paymentRef := strings.TrimSpace(req.GetPaymentRef())
	if paymentRef == "" {
		return gsresponse.InvalidArgument[orchestratorv1.GetPaymentResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("payment_ref is required"))
	}
	store := s.storage.Payments()
	if store == nil {
		return gsresponse.Unavailable[orchestratorv1.GetPaymentResponse](s.logger, mservice.PaymentOrchestrator, errStorageUnavailable)
	}
	entity, err := store.GetByPaymentRef(ctx, paymentRef)
	if err != nil {
		if err == storage.ErrPaymentNotFound {
			return gsresponse.NotFound[orchestratorv1.GetPaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
		}
		return gsresponse.Auto[orchestratorv1.GetPaymentResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	return gsresponse.Success(&orchestratorv1.GetPaymentResponse{Payment: toProtoPayment(entity)})
}

func (s *Service) listPaymentsHandler(ctx context.Context, req *orchestratorv1.ListPaymentsRequest) gsresponse.Responder[orchestratorv1.ListPaymentsResponse] {
	if err := s.ensureRepository(ctx); err != nil {
		return gsresponse.Unavailable[orchestratorv1.ListPaymentsResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	if req == nil {
		return gsresponse.InvalidArgument[orchestratorv1.ListPaymentsResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("nil request"))
	}
	store := s.storage.Payments()
	if store == nil {
		return gsresponse.Unavailable[orchestratorv1.ListPaymentsResponse](s.logger, mservice.PaymentOrchestrator, errStorageUnavailable)
	}
	filter := filterFromProto(req)
	result, err := store.List(ctx, filter)
	if err != nil {
		return gsresponse.Auto[orchestratorv1.ListPaymentsResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	resp := &orchestratorv1.ListPaymentsResponse{
		Page: &paginationv1.CursorPageResponse{
			NextCursor: result.NextCursor,
		},
	}
	resp.Payments = make([]*orchestratorv1.Payment, 0, len(result.Items))
	for _, item := range result.Items {
		resp.Payments = append(resp.Payments, toProtoPayment(item))
	}
	return gsresponse.Success(resp)
}

func (s *Service) initiateConversionHandler(ctx context.Context, req *orchestratorv1.InitiateConversionRequest) gsresponse.Responder[orchestratorv1.InitiateConversionResponse] {
	if err := s.ensureRepository(ctx); err != nil {
		return gsresponse.Unavailable[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	if req == nil {
		return gsresponse.InvalidArgument[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("nil request"))
	}
	meta := req.GetMeta()
	if meta == nil {
		return gsresponse.InvalidArgument[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("meta is required"))
	}
	orgRef := strings.TrimSpace(meta.GetOrganizationRef())
	if orgRef == "" {
		return gsresponse.InvalidArgument[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("organization_ref is required"))
	}
	orgObjectID, parseErr := primitive.ObjectIDFromHex(orgRef)
	if parseErr != nil {
		return gsresponse.InvalidArgument[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("organization_ref must be a valid objectID"))
	}
	idempotencyKey := strings.TrimSpace(req.GetIdempotencyKey())
	if idempotencyKey == "" {
		return gsresponse.InvalidArgument[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("idempotency_key is required"))
	}
	if req.GetSource() == nil || req.GetSource().GetLedger() == nil {
		return gsresponse.InvalidArgument[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("source ledger endpoint is required"))
	}
	if req.GetDestination() == nil || req.GetDestination().GetLedger() == nil {
		return gsresponse.InvalidArgument[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("destination ledger endpoint is required"))
	}
	fxIntent := req.GetFx()
	if fxIntent == nil {
		return gsresponse.InvalidArgument[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("fx intent is required"))
	}

	store := s.storage.Payments()
	if store == nil {
		return gsresponse.Unavailable[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, errStorageUnavailable)
	}

	if existing, err := store.GetByIdempotencyKey(ctx, orgObjectID, idempotencyKey); err == nil && existing != nil {
		return gsresponse.Success(&orchestratorv1.InitiateConversionResponse{Conversion: toProtoPayment(existing)})
	} else if err != nil && err != storage.ErrPaymentNotFound {
		return gsresponse.Auto[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, err)
	}

	amount, err := conversionAmountFromMetadata(req.GetMetadata(), fxIntent)
	if err != nil {
		return gsresponse.InvalidArgument[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, err)
	}

	intentProto := &orchestratorv1.PaymentIntent{
		Kind:        orchestratorv1.PaymentKind_PAYMENT_KIND_FX_CONVERSION,
		Source:      req.GetSource(),
		Destination: req.GetDestination(),
		Amount:      amount,
		RequiresFx:  true,
		Fx:          fxIntent,
		FeePolicy:   req.GetFeePolicy(),
	}

	quote, err := s.buildPaymentQuote(ctx, orgRef, &orchestratorv1.QuotePaymentRequest{
		Meta:           req.GetMeta(),
		IdempotencyKey: req.GetIdempotencyKey(),
		Intent:         intentProto,
	})
	if err != nil {
		return gsresponse.Auto[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, err)
	}

	entity := &model.Payment{}
	entity.SetID(primitive.NewObjectID())
	entity.SetOrganizationRef(orgObjectID)
	entity.PaymentRef = entity.GetID().Hex()
	entity.IdempotencyKey = idempotencyKey
	entity.State = model.PaymentStateAccepted
	entity.Intent = intentFromProto(intentProto)
	entity.Metadata = cloneMetadata(req.GetMetadata())
	entity.LastQuote = quoteSnapshotToModel(quote)
	entity.Normalize()

	if err = store.Create(ctx, entity); err != nil {
		if err == storage.ErrDuplicatePayment {
			return gsresponse.Auto[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, merrors.DataConflict("payment already exists"))
		}
		return gsresponse.Auto[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, err)
	}

	if err := s.executePayment(ctx, store, entity, quote); err != nil {
		return gsresponse.Auto[orchestratorv1.InitiateConversionResponse](s.logger, mservice.PaymentOrchestrator, err)
	}

	return gsresponse.Success(&orchestratorv1.InitiateConversionResponse{
		Conversion: toProtoPayment(entity),
	})
}

func (s *Service) processTransferUpdateHandler(ctx context.Context, req *orchestratorv1.ProcessTransferUpdateRequest) gsresponse.Responder[orchestratorv1.ProcessTransferUpdateResponse] {
	if err := s.ensureRepository(ctx); err != nil {
		return gsresponse.Unavailable[orchestratorv1.ProcessTransferUpdateResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	if req == nil || req.GetEvent() == nil || req.GetEvent().GetTransfer() == nil {
		return gsresponse.InvalidArgument[orchestratorv1.ProcessTransferUpdateResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("transfer event is required"))
	}
	transfer := req.GetEvent().GetTransfer()
	transferRef := strings.TrimSpace(transfer.GetTransferRef())
	if transferRef == "" {
		return gsresponse.InvalidArgument[orchestratorv1.ProcessTransferUpdateResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("transfer_ref is required"))
	}
	store := s.storage.Payments()
	if store == nil {
		return gsresponse.Unavailable[orchestratorv1.ProcessTransferUpdateResponse](s.logger, mservice.PaymentOrchestrator, errStorageUnavailable)
	}
	payment, err := store.GetByChainTransferRef(ctx, transferRef)
	if err != nil {
		if err == storage.ErrPaymentNotFound {
			return gsresponse.NotFound[orchestratorv1.ProcessTransferUpdateResponse](s.logger, mservice.PaymentOrchestrator, err)
		}
		return gsresponse.Auto[orchestratorv1.ProcessTransferUpdateResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	applyTransferStatus(req.GetEvent(), payment)
	if err := store.Update(ctx, payment); err != nil {
		return gsresponse.Auto[orchestratorv1.ProcessTransferUpdateResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	return gsresponse.Success(&orchestratorv1.ProcessTransferUpdateResponse{Payment: toProtoPayment(payment)})
}

func (s *Service) processDepositObservedHandler(ctx context.Context, req *orchestratorv1.ProcessDepositObservedRequest) gsresponse.Responder[orchestratorv1.ProcessDepositObservedResponse] {
	if err := s.ensureRepository(ctx); err != nil {
		return gsresponse.Unavailable[orchestratorv1.ProcessDepositObservedResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	if req == nil || req.GetEvent() == nil {
		return gsresponse.InvalidArgument[orchestratorv1.ProcessDepositObservedResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("deposit event is required"))
	}
	event := req.GetEvent()
	walletRef := strings.TrimSpace(event.GetWalletRef())
	if walletRef == "" {
		return gsresponse.InvalidArgument[orchestratorv1.ProcessDepositObservedResponse](s.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("wallet_ref is required"))
	}
	store := s.storage.Payments()
	if store == nil {
		return gsresponse.Unavailable[orchestratorv1.ProcessDepositObservedResponse](s.logger, mservice.PaymentOrchestrator, errStorageUnavailable)
	}
	filter := &model.PaymentFilter{
		States:         []model.PaymentState{model.PaymentStateSubmitted, model.PaymentStateFundsReserved},
		DestinationRef: walletRef,
	}
	result, err := store.List(ctx, filter)
	if err != nil {
		return gsresponse.Auto[orchestratorv1.ProcessDepositObservedResponse](s.logger, mservice.PaymentOrchestrator, err)
	}
	for _, payment := range result.Items {
		if payment.Intent.Destination.Type != model.EndpointTypeManagedWallet {
			continue
		}
		if !moneyEquals(payment.Intent.Amount, event.GetAmount()) {
			continue
		}
		payment.State = model.PaymentStateSettled
		payment.FailureCode = model.PaymentFailureCodeUnspecified
		payment.FailureReason = ""
		if payment.Execution == nil {
			payment.Execution = &model.ExecutionRefs{}
		}
		if payment.Execution.ChainTransferRef == "" {
			payment.Execution.ChainTransferRef = strings.TrimSpace(event.GetTransactionHash())
		}
		if err := store.Update(ctx, payment); err != nil {
			return gsresponse.Auto[orchestratorv1.ProcessDepositObservedResponse](s.logger, mservice.PaymentOrchestrator, err)
		}
		return gsresponse.Success(&orchestratorv1.ProcessDepositObservedResponse{Payment: toProtoPayment(payment)})
	}
	return gsresponse.Success(&orchestratorv1.ProcessDepositObservedResponse{})
}
