package quote_request_validator_v2

import (
	"errors"
	"fmt"
	"strings"

	"github.com/tech/sendico/pkg/merrors"
	payecon "github.com/tech/sendico/pkg/payments/economics"
	paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1"
	endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
	quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
	sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
	"go.mongodb.org/mongo-driver/v2/bson"
)

var (
	ErrIdempotencyRequired    = errors.New("idempotency key is required")
	ErrPreviewWithIdempotency = errors.New("preview requests must not use idempotency key")
	ErrInitiatorRefRequired   = errors.New("initiator_ref is required")
)

type Context struct {
	OrganizationRef string
	OrganizationID  bson.ObjectID
	IdempotencyKey  string
	InitiatorRef    string
	PreviewOnly     bool
	IntentCount     int
}

type QuoteRequestValidatorV2 struct{}

func New() *QuoteRequestValidatorV2 {
	return &QuoteRequestValidatorV2{}
}

func (v *QuoteRequestValidatorV2) ValidateQuotePayment(req *quotationv2.QuotePaymentRequest) (*Context, error) {
	if req == nil {
		return nil, merrors.InvalidArgument("nil request")
	}

	orgRef, orgID, err := validateMeta(req.GetMeta())
	if err != nil {
		return nil, err
	}
	if err := validateQuoteIntent(req.GetIntent(), "intent"); err != nil {
		return nil, err
	}

	idempotencyKey := strings.TrimSpace(req.GetIdempotencyKey())
	previewOnly := req.GetPreviewOnly()
	if err := validateIdempotency(idempotencyKey, previewOnly); err != nil {
		return nil, err
	}

	initiatorRef := strings.TrimSpace(req.GetInitiatorRef())
	if initiatorRef == "" {
		return nil, ErrInitiatorRefRequired
	}

	return &Context{
		OrganizationRef: orgRef,
		OrganizationID:  orgID,
		IdempotencyKey:  idempotencyKey,
		InitiatorRef:    initiatorRef,
		PreviewOnly:     previewOnly,
		IntentCount:     1,
	}, nil
}

func (v *QuoteRequestValidatorV2) ValidateQuotePayments(req *quotationv2.QuotePaymentsRequest) (*Context, error) {
	if req == nil {
		return nil, merrors.InvalidArgument("nil request")
	}

	orgRef, orgID, err := validateMeta(req.GetMeta())
	if err != nil {
		return nil, err
	}

	intents := req.GetIntents()
	if len(intents) == 0 {
		return nil, merrors.InvalidArgument("intents are required")
	}
	for i, intent := range intents {
		if err := validateQuoteIntent(intent, fmt.Sprintf("intents[%d]", i)); err != nil {
			return nil, err
		}
	}

	idempotencyKey := strings.TrimSpace(req.GetIdempotencyKey())
	previewOnly := req.GetPreviewOnly()
	if err := validateIdempotency(idempotencyKey, previewOnly); err != nil {
		return nil, err
	}

	initiatorRef := strings.TrimSpace(req.GetInitiatorRef())
	if initiatorRef == "" {
		return nil, ErrInitiatorRefRequired
	}

	return &Context{
		OrganizationRef: orgRef,
		OrganizationID:  orgID,
		IdempotencyKey:  idempotencyKey,
		InitiatorRef:    initiatorRef,
		PreviewOnly:     previewOnly,
		IntentCount:     len(intents),
	}, nil
}

func validateMeta(meta *sharedv1.RequestMeta) (string, bson.ObjectID, error) {
	if meta == nil {
		return "", bson.NilObjectID, merrors.InvalidArgument("meta is required")
	}

	orgRef := strings.TrimSpace(meta.GetOrganizationRef())
	if orgRef == "" {
		return "", bson.NilObjectID, merrors.InvalidArgument("organization_ref is required")
	}

	orgID, err := bson.ObjectIDFromHex(orgRef)
	if err != nil {
		return "", bson.NilObjectID, merrors.InvalidArgument("organization_ref must be a valid objectID")
	}

	return orgRef, orgID, nil
}

func validateQuoteIntent(intent *quotationv2.QuoteIntent, field string) error {
	if intent == nil {
		return merrors.InvalidArgument(field + " is required")
	}
	if !hasEndpointValue(intent.GetSource()) {
		return merrors.InvalidArgument(field + ".source is required")
	}
	if !hasEndpointValue(intent.GetDestination()) {
		return merrors.InvalidArgument(field + ".destination is required")
	}
	if intent.GetAmount() == nil {
		return merrors.InvalidArgument(field + ".amount is required")
	}
	if err := validateSettlementAndFeeTreatment(intent.GetSettlementMode(), intent.GetFeeTreatment(), field); err != nil {
		return err
	}
	return nil
}

func validateSettlementAndFeeTreatment(
	mode paymentv1.SettlementMode,
	feeTreatment quotationv2.FeeTreatment,
	field string,
) error {
	if !payecon.IsValidSettlementMode(mode) {
		return merrors.InvalidArgument(field + ".settlement_mode is invalid")
	}
	if !payecon.IsValidFeeTreatment(feeTreatment) {
		return merrors.InvalidArgument(field + ".fee_treatment is invalid")
	}
	return nil
}

func hasEndpointValue(endpoint *endpointv1.PaymentEndpoint) bool {
	if endpoint == nil {
		return false
	}
	if strings.TrimSpace(endpoint.GetPaymentMethodRef()) != "" {
		return true
	}
	if endpoint.GetPaymentMethod() != nil {
		return true
	}
	return strings.TrimSpace(endpoint.GetPayeeRef()) != ""
}

func validateIdempotency(idempotencyKey string, previewOnly bool) error {
	if previewOnly && idempotencyKey != "" {
		return ErrPreviewWithIdempotency
	}
	if !previewOnly && idempotencyKey == "" {
		return ErrIdempotencyRequired
	}
	return nil
}
