package orchestrator

import (
	"context"

	"github.com/tech/sendico/payments/orchestrator/storage"
	"github.com/tech/sendico/pkg/api/routers/gsresponse"
	"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.uber.org/zap"
)

type paymentQueryHandler struct {
	repo       storage.Repository
	ensureRepo func(ctx context.Context) error
	logger     mlogger.Logger
}

func newPaymentQueryHandler(repo storage.Repository, ensure func(ctx context.Context) error, logger mlogger.Logger) *paymentQueryHandler {
	return &paymentQueryHandler{
		repo:       repo,
		ensureRepo: ensure,
		logger:     logger,
	}
}

func (h *paymentQueryHandler) getPayment(ctx context.Context, req *orchestratorv1.GetPaymentRequest) gsresponse.Responder[orchestratorv1.GetPaymentResponse] {
	if err := h.ensureRepo(ctx); err != nil {
		return gsresponse.Unavailable[orchestratorv1.GetPaymentResponse](h.logger, mservice.PaymentOrchestrator, err)
	}
	if req == nil {
		return gsresponse.InvalidArgument[orchestratorv1.GetPaymentResponse](h.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("nil request"))
	}
	paymentRef, err := requirePaymentRef(req.GetPaymentRef())
	if err != nil {
		return gsresponse.InvalidArgument[orchestratorv1.GetPaymentResponse](h.logger, mservice.PaymentOrchestrator, err)
	}
	store, err := ensurePaymentsStore(h.repo)
	if err != nil {
		return gsresponse.Unavailable[orchestratorv1.GetPaymentResponse](h.logger, mservice.PaymentOrchestrator, err)
	}
	entity, err := store.GetByPaymentRef(ctx, paymentRef)
	if err != nil {
		return paymentNotFoundResponder[orchestratorv1.GetPaymentResponse](mservice.PaymentOrchestrator, h.logger, err)
	}
	h.logger.Debug("payment fetched", zap.String("payment_ref", paymentRef))
	return gsresponse.Success(&orchestratorv1.GetPaymentResponse{Payment: toProtoPayment(entity)})
}

func (h *paymentQueryHandler) listPayments(ctx context.Context, req *orchestratorv1.ListPaymentsRequest) gsresponse.Responder[orchestratorv1.ListPaymentsResponse] {
	if err := h.ensureRepo(ctx); err != nil {
		return gsresponse.Unavailable[orchestratorv1.ListPaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
	}
	if req == nil {
		return gsresponse.InvalidArgument[orchestratorv1.ListPaymentsResponse](h.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("nil request"))
	}
	store, err := ensurePaymentsStore(h.repo)
	if err != nil {
		return gsresponse.Unavailable[orchestratorv1.ListPaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
	}
	filter := filterFromProto(req)
	result, err := store.List(ctx, filter)
	if err != nil {
		return gsresponse.Auto[orchestratorv1.ListPaymentsResponse](h.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))
	}
	h.logger.Debug("payments listed", zap.Int("count", len(resp.Payments)), zap.String("next_cursor", resp.GetPage().GetNextCursor()))
	return gsresponse.Success(resp)
}
