package methods

import (
	"context"

	"github.com/tech/sendico/pkg/merrors"
	pkgmodel "github.com/tech/sendico/pkg/model"
	methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1"
	"go.mongodb.org/mongo-driver/v2/bson"
)

const (
	paymentTypeAccount              pkgmodel.PaymentType = 8
	maxPrivateMethodResolutionDepth int                  = 8
)

func (s *Service) GetPaymentMethodPrivate(ctx context.Context, req *methodsv1.GetPaymentMethodPrivateRequest) (*methodsv1.GetPaymentMethodPrivateResponse, error) {
	if req == nil {
		return autoError[methodsv1.GetPaymentMethodPrivateResponse](ctx, s.logger, merrors.InvalidArgument("request is required"))
	}
	if s.pmstore == nil {
		return autoError[methodsv1.GetPaymentMethodPrivateResponse](ctx, s.logger, errStoreUnavailable)
	}

	if req.GetEndpoint() == methodsv1.PrivateEndpoint_PRIVATE_ENDPOINT_UNSPECIFIED {
		return autoError[methodsv1.GetPaymentMethodPrivateResponse](ctx, s.logger, merrors.InvalidArgument("endpoint is required", "endpoint"))
	}

	organizationRef, err := parseObjectID(req.GetOrganizationRef(), "organization_ref")
	if err != nil {
		return autoError[methodsv1.GetPaymentMethodPrivateResponse](ctx, s.logger, err)
	}

	var resolved *pkgmodel.PaymentMethod
	switch req.GetSelector().(type) {
	case *methodsv1.GetPaymentMethodPrivateRequest_PaymentMethodRef:
		methodRef, err := parseObjectID(req.GetPaymentMethodRef(), "payment_method_ref")
		if err != nil {
			return autoError[methodsv1.GetPaymentMethodPrivateResponse](ctx, s.logger, err)
		}
		resolved, err = s.resolvePrivateByMethodRef(ctx, organizationRef, methodRef, req.GetEndpoint(), 0)
		if err != nil {
			return autoError[methodsv1.GetPaymentMethodPrivateResponse](ctx, s.logger, err)
		}
	case *methodsv1.GetPaymentMethodPrivateRequest_PayeeRef:
		payeeRef, err := parseObjectID(req.GetPayeeRef(), "payee_ref")
		if err != nil {
			return autoError[methodsv1.GetPaymentMethodPrivateResponse](ctx, s.logger, err)
		}
		resolved, err = s.resolvePrivateByRecipientRef(ctx, organizationRef, payeeRef, req.GetEndpoint(), 0)
		if err != nil {
			return autoError[methodsv1.GetPaymentMethodPrivateResponse](ctx, s.logger, err)
		}
	default:
		return autoError[methodsv1.GetPaymentMethodPrivateResponse](ctx, s.logger, merrors.InvalidArgument(
			"selector must include payment_method_ref or payee_ref",
			"selector",
		))
	}

	record, err := encodePaymentMethodRecord(resolved)
	if err != nil {
		return autoError[methodsv1.GetPaymentMethodPrivateResponse](ctx, s.logger, err)
	}

	return &methodsv1.GetPaymentMethodPrivateResponse{
		PaymentMethodRecord: record,
	}, nil
}

func (s *Service) resolvePrivateByMethodRef(
	ctx context.Context,
	organizationRef bson.ObjectID,
	methodRef bson.ObjectID,
	endpoint methodsv1.PrivateEndpoint,
	depth int,
) (*pkgmodel.PaymentMethod, error) {
	method, err := s.pmstore.GetPrivate(ctx, methodRef)
	if err != nil {
		return nil, err
	}
	return s.resolvePrivateMethod(ctx, organizationRef, method, endpoint, depth)
}

func (s *Service) resolvePrivateByRecipientRef(
	ctx context.Context,
	organizationRef bson.ObjectID,
	recipientRef bson.ObjectID,
	endpoint methodsv1.PrivateEndpoint,
	depth int,
) (*pkgmodel.PaymentMethod, error) {
	items, err := s.pmstore.ListPrivate(ctx, organizationRef, recipientRef, nil)
	if err != nil {
		return nil, err
	}
	if len(items) == 0 {
		return nil, merrors.InvalidArgument("no payment methods available for recipient")
	}

	selected := pickPreferredPrivateMethod(items, endpoint)
	if selected == nil {
		return nil, merrors.InvalidArgument("no routable payment methods available for recipient")
	}
	return s.resolvePrivateMethod(ctx, organizationRef, selected, endpoint, depth)
}

func (s *Service) resolvePrivateMethod(
	ctx context.Context,
	organizationRef bson.ObjectID,
	method *pkgmodel.PaymentMethod,
	endpoint methodsv1.PrivateEndpoint,
	depth int,
) (*pkgmodel.PaymentMethod, error) {
	if method == nil {
		return nil, merrors.InvalidArgument("payment method is required")
	}
	if depth >= maxPrivateMethodResolutionDepth {
		return nil, merrors.InvalidArgument("payment method resolution depth exceeded")
	}

	if methodIsAccount(method) {
		if method.RecipientRef.IsZero() {
			return nil, merrors.InvalidArgument("account payment method recipient_ref is required")
		}
		return s.resolvePrivateByRecipientRef(ctx, organizationRef, method.RecipientRef, endpoint, depth+1)
	}
	return method, nil
}

func methodIsAccount(method *pkgmodel.PaymentMethod) bool {
	if method == nil {
		return false
	}
	return method.Type == paymentTypeAccount
}

func pickPreferredPrivateMethod(items []pkgmodel.PaymentMethod, endpoint methodsv1.PrivateEndpoint) *pkgmodel.PaymentMethod {
	switch endpoint {
	case methodsv1.PrivateEndpoint_PRIVATE_ENDPOINT_SOURCE:
		return pickMainThenAnyNonAccount(items)
	case methodsv1.PrivateEndpoint_PRIVATE_ENDPOINT_DESTINATION:
		return pickMainThenAnyNonAccount(items)
	default:
		return nil
	}
}

func pickMainThenAnyNonAccount(items []pkgmodel.PaymentMethod) *pkgmodel.PaymentMethod {
	for i := range items {
		if items[i].IsMain && !methodIsAccount(&items[i]) {
			return &items[i]
		}
	}
	for i := range items {
		if !methodIsAccount(&items[i]) {
			return &items[i]
		}
	}
	for i := range items {
		if items[i].IsMain {
			return &items[i]
		}
	}
	if len(items) == 0 {
		return nil
	}
	return &items[0]
}
