package orchestrator

import (
	"strings"

	"github.com/tech/sendico/payments/orchestrator/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/payments/rail"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
)

func (p *paymentExecutor) buildCryptoTransferRequest(payment *model.Payment, amount *paymenttypes.Money, action model.RailOperation, idempotencyKey string, quote *orchestratorv1.PaymentQuote) (rail.TransferRequest, error) {
	if payment == nil {
		return rail.TransferRequest{}, merrors.InvalidArgument("chain: payment is required")
	}
	if amount == nil {
		return rail.TransferRequest{}, merrors.InvalidArgument("chain: amount is required")
	}
	source := payment.Intent.Source.ManagedWallet
	if source == nil || strings.TrimSpace(source.ManagedWalletRef) == "" {
		return rail.TransferRequest{}, merrors.InvalidArgument("chain: source managed wallet is required")
	}
	destRef, memo, err := p.resolveCryptoDestination(payment, action)
	if err != nil {
		return rail.TransferRequest{}, err
	}
	req := rail.TransferRequest{
		OrganizationRef: payment.OrganizationRef.Hex(),
		FromAccountID:   strings.TrimSpace(source.ManagedWalletRef),
		ToAccountID:     strings.TrimSpace(destRef),
		Currency:        strings.TrimSpace(amount.GetCurrency()),
		Network:         strings.TrimSpace(cryptoNetworkForPayment(payment)),
		Amount:          strings.TrimSpace(amount.GetAmount()),
		IdempotencyKey:  strings.TrimSpace(idempotencyKey),
		Metadata:        cloneMetadata(payment.Metadata),
		ClientReference: payment.PaymentRef,
		DestinationMemo: memo,
	}
	if req.Currency == "" || req.Amount == "" {
		return rail.TransferRequest{}, merrors.InvalidArgument("chain: amount is required")
	}
	if req.IdempotencyKey == "" {
		return rail.TransferRequest{}, merrors.InvalidArgument("chain: idempotency_key is required")
	}
	if action == model.RailOperationSend && quote != nil {
		req.Fees = feeBreakdownFromQuote(quote)
	}
	return req, nil
}

func (p *paymentExecutor) resolveCryptoDestination(payment *model.Payment, action model.RailOperation) (string, string, error) {
	if payment == nil {
		return "", "", merrors.InvalidArgument("chain: payment is required")
	}
	intent := payment.Intent
	switch intent.Destination.Type {
	case model.EndpointTypeManagedWallet:
		if action == model.RailOperationSend {
			if intent.Destination.ManagedWallet == nil || strings.TrimSpace(intent.Destination.ManagedWallet.ManagedWalletRef) == "" {
				return "", "", merrors.InvalidArgument("chain: destination managed wallet is required")
			}
			return strings.TrimSpace(intent.Destination.ManagedWallet.ManagedWalletRef), "", nil
		}
	case model.EndpointTypeExternalChain:
		if action == model.RailOperationSend {
			if intent.Destination.ExternalChain == nil || strings.TrimSpace(intent.Destination.ExternalChain.Address) == "" {
				return "", "", merrors.InvalidArgument("chain: external address is required")
			}
			return strings.TrimSpace(intent.Destination.ExternalChain.Address), strings.TrimSpace(intent.Destination.ExternalChain.Memo), nil
		}
	}
	route, err := p.resolveCardRoute(intent)
	if err != nil {
		return "", "", err
	}
	switch action {
	case model.RailOperationSend:
		address := strings.TrimSpace(route.FundingAddress)
		if address == "" {
			return "", "", merrors.InvalidArgument("chain: funding address is required")
		}
		return address, "", nil
	case model.RailOperationFee:
		if walletRef := strings.TrimSpace(route.FeeWalletRef); walletRef != "" {
			return walletRef, "", nil
		}
		if address := strings.TrimSpace(route.FeeAddress); address != "" {
			return address, "", nil
		}
		return "", "", merrors.InvalidArgument("chain: fee destination is required")
	default:
		return "", "", merrors.InvalidArgument("chain: unsupported action")
	}
}

func cryptoNetworkForPayment(payment *model.Payment) string {
	if payment == nil {
		return ""
	}
	network := networkFromEndpoint(payment.Intent.Source)
	if network != "" {
		return network
	}
	return networkFromEndpoint(payment.Intent.Destination)
}
