package orchestrator

import (
	"context"
	"strings"
	"time"

	"github.com/tech/sendico/payments/orchestrator/storage/model"
	"github.com/tech/sendico/pkg/api/routers/gsresponse"
	"github.com/tech/sendico/pkg/mservice"
	feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
	fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
	mntxv1 "github.com/tech/sendico/pkg/proto/gateway/mntx/v1"
	orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
)

func (s *Service) ensureRepository(ctx context.Context) error {
	if s.storage == nil {
		return errStorageUnavailable
	}
	return s.storage.Ping(ctx)
}

func (s *Service) withTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
	if d <= 0 {
		return context.WithCancel(ctx)
	}
	return context.WithTimeout(ctx, d)
}

func executeUnary[TReq any, TResp any](ctx context.Context, svc *Service, method string, handler func(context.Context, *TReq) gsresponse.Responder[TResp], req *TReq) (*TResp, error) {
	start := svc.clock.Now()
	resp, err := gsresponse.Unary(svc.logger, mservice.PaymentOrchestrator, handler)(ctx, req)
	observeRPC(method, err, svc.clock.Now().Sub(start))
	return resp, err
}

func triggerFromKind(kind orchestratorv1.PaymentKind, requiresFX bool) feesv1.Trigger {
	switch kind {
	case orchestratorv1.PaymentKind_PAYMENT_KIND_PAYOUT:
		return feesv1.Trigger_TRIGGER_PAYOUT
	case orchestratorv1.PaymentKind_PAYMENT_KIND_INTERNAL_TRANSFER:
		return feesv1.Trigger_TRIGGER_CAPTURE
	case orchestratorv1.PaymentKind_PAYMENT_KIND_FX_CONVERSION:
		return feesv1.Trigger_TRIGGER_FX_CONVERSION
	default:
		if requiresFX {
			return feesv1.Trigger_TRIGGER_FX_CONVERSION
		}
		return feesv1.Trigger_TRIGGER_UNSPECIFIED
	}
}

func shouldEstimateNetworkFee(intent *orchestratorv1.PaymentIntent) bool {
	if intent == nil {
		return false
	}
	dest := intent.GetDestination()
	if dest == nil {
		return false
	}
	if dest.GetCard() != nil {
		return false
	}
	if intent.GetKind() == orchestratorv1.PaymentKind_PAYMENT_KIND_PAYOUT {
		return true
	}
	if dest.GetManagedWallet() != nil || dest.GetExternalChain() != nil {
		return true
	}
	return false
}

func shouldRequestFX(intent *orchestratorv1.PaymentIntent) bool {
	if intent == nil {
		return false
	}
	if fxIntentForQuote(intent) != nil {
		return true
	}
	return intent.GetRequiresFx()
}

func fxIntentForQuote(intent *orchestratorv1.PaymentIntent) *orchestratorv1.FXIntent {
	if intent == nil {
		return nil
	}
	if fx := intent.GetFx(); fx != nil && fx.GetPair() != nil {
		return fx
	}
	amount := intent.GetAmount()
	if amount == nil {
		return nil
	}
	settlementCurrency := strings.TrimSpace(intent.GetSettlementCurrency())
	if settlementCurrency == "" {
		return nil
	}
	if strings.EqualFold(amount.GetCurrency(), settlementCurrency) {
		return nil
	}
	return &orchestratorv1.FXIntent{
		Pair: &fxv1.CurrencyPair{
			Base:  strings.TrimSpace(amount.GetCurrency()),
			Quote: settlementCurrency,
		},
		Side: fxv1.Side_SELL_BASE_BUY_QUOTE,
	}
}

func mapMntxStatusToState(status mntxv1.PayoutStatus) model.PaymentState {
	switch status {
	case mntxv1.PayoutStatus_PAYOUT_STATUS_PROCESSED:
		return model.PaymentStateSettled
	case mntxv1.PayoutStatus_PAYOUT_STATUS_FAILED:
		return model.PaymentStateFailed
	case mntxv1.PayoutStatus_PAYOUT_STATUS_PENDING:
		return model.PaymentStateSubmitted
	default:
		return model.PaymentStateUnspecified
	}
}
