package psvc

import (
	"context"
	"strings"
	"time"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/pquery"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/prmap"
	"github.com/tech/sendico/pkg/merrors"
	paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
	orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
	"go.uber.org/zap"
)

func (s *svc) GetPayment(ctx context.Context, req *orchestrationv2.GetPaymentRequest) (resp *orchestrationv2.GetPaymentResponse, err error) {
	logger := s.logger
	orgRef := ""
	if req != nil && req.GetMeta() != nil {
		orgRef = strings.TrimSpace(req.GetMeta().GetOrganizationRef())
	}
	logger.Debug("Starting Get payment",
		zap.String("organization_ref", orgRef),
		zap.String("payment_ref", strings.TrimSpace(req.GetPaymentRef())),
	)
	defer func(start time.Time) {
		fields := []zap.Field{zap.Int64("duration_ms", time.Since(start).Milliseconds())}
		if resp != nil && resp.Payment != nil {
			fields = append(fields,
				zap.String("state", resp.Payment.GetState().String()),
				zap.Uint64("version", resp.Payment.GetVersion()),
			)
		}
		if err != nil {
			logger.Warn("Failed to get payment", append(fields, zap.Error(err))...)
			return
		}
		logger.Debug("Completed Get payment", fields...)
	}(time.Now())

	if req == nil {
		return nil, merrors.InvalidArgument("request is required")
	}
	_, orgID, hasOrg, err := parseOptionalOrganization(req.GetMeta())
	if err != nil {
		return nil, err
	}
	paymentRef, err := parsePaymentRef(req.GetPaymentRef())
	if err != nil {
		return nil, err
	}

	queryInput := pquery.GetPaymentInput{PaymentRef: paymentRef}
	if hasOrg {
		queryInput.OrganizationRef = orgID
	}
	payment, err := s.query.GetPayment(ctx, queryInput)
	if err != nil {
		return nil, err
	}
	protoPayment, err := s.mapPayment(payment)
	if err != nil {
		return nil, err
	}
	resp = &orchestrationv2.GetPaymentResponse{
		Payment:         protoPayment,
		OrganizationRef: payment.OrganizationRef.Hex(),
	}
	return resp, nil
}

func (s *svc) ListPayments(ctx context.Context, req *orchestrationv2.ListPaymentsRequest) (resp *orchestrationv2.ListPaymentsResponse, err error) {
	logger := s.logger
	orgRef := ""
	if req != nil && req.GetMeta() != nil {
		orgRef = strings.TrimSpace(req.GetMeta().GetOrganizationRef())
	}
	logger.Debug("Starting List payments",
		zap.String("organization_ref", orgRef),
		zap.String("quotation_ref", strings.TrimSpace(req.GetQuotationRef())),
		zap.Int("states_count", len(req.GetStates())),
	)
	defer func(start time.Time) {
		fields := []zap.Field{zap.Int64("duration_ms", time.Since(start).Milliseconds())}
		if resp != nil {
			fields = append(fields, zap.Int("payments_count", len(resp.GetPayments())))
		}
		if err != nil {
			logger.Warn("Failed to list payments", append(fields, zap.Error(err))...)
			return
		}
		logger.Debug("Completed List payments", fields...)
	}(time.Now())

	_, orgID, err := parseOrganization(req.GetMeta())
	if err != nil {
		return nil, err
	}
	states, err := mapStates(req.GetStates())
	if err != nil {
		return nil, err
	}
	createdFrom, err := parseCreated(req.GetCreatedFrom(), "created_from")
	if err != nil {
		return nil, err
	}
	createdTo, err := parseCreated(req.GetCreatedTo(), "created_to")
	if err != nil {
		return nil, err
	}
	cursor, err := parseCursor(req.GetPage().GetCursor())
	if err != nil {
		return nil, err
	}
	limit := int32(0)
	if req.GetPage() != nil {
		limit = req.GetPage().GetLimit()
	}

	page, err := s.query.ListPayments(ctx, pquery.ListPaymentsInput{
		OrganizationRef: orgID,
		States:          states,
		QuotationRef:    strings.TrimSpace(req.GetQuotationRef()),
		CreatedFrom:     createdFrom,
		CreatedTo:       createdTo,
		Cursor:          cursor,
		Limit:           limit,
	})
	if err != nil {
		return nil, err
	}

	items, err := s.mapPayments(page.Items)
	if err != nil {
		return nil, err
	}
	nextCursor, err := formatCursor(page.NextCursor)
	if err != nil {
		return nil, err
	}
	resp = &orchestrationv2.ListPaymentsResponse{
		Payments: items,
		Page:     &paginationv1.CursorPageResponse{NextCursor: nextCursor},
	}
	return resp, nil
}

func (s *svc) mapPayments(items []*agg.Payment) ([]*orchestrationv2.Payment, error) {
	if len(items) == 0 {
		return nil, nil //nolint:nilnil // nil slice means no payments in current page
	}
	out := make([]*orchestrationv2.Payment, 0, len(items))
	for i := range items {
		mapped, err := s.mapPayment(items[i])
		if err != nil {
			return nil, err
		}
		out = append(out, mapped)
	}
	return out, nil
}

func (s *svc) mapPayment(payment *agg.Payment) (*orchestrationv2.Payment, error) {
	mapped, err := s.mapper.Map(prmap.MapInput{Payment: payment})
	if err != nil {
		return nil, err
	}
	if mapped == nil {
		return nil, nil //nolint:nilnil // nil mapped output means mapper produced no payment payload
	}
	return mapped.Payment, nil
}
