package transfer_intent_hydrator

import (
	"fmt"
	"strings"

	chainpkg "github.com/tech/sendico/pkg/chain"
	"github.com/tech/sendico/pkg/merrors"
	pkgmodel "github.com/tech/sendico/pkg/model"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
)

func hydrateFromPaymentMethod(method *endpointv1.PaymentMethod, field string) (QuoteEndpoint, error) {
	if method == nil {
		return QuoteEndpoint{}, merrors.InvalidArgument(field + " is required")
	}
	switch method.GetType() {
	case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_WALLET:
		return hydrateWalletMethod(method, field)
	case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CRYPTO_ADDRESS:
		return hydrateCryptoAddressMethod(method, field)
	case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD:
		return hydrateCardMethod(method, field)
	case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD_TOKEN:
		return hydrateCardTokenMethod(method, field)
	case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_LEDGER:
		return hydrateLedgerMethod(method, field)
	default:
		return QuoteEndpoint{}, merrors.InvalidArgument(
			fmt.Sprintf("%s uses unsupported payment method type: %s", field, method.GetType().String()),
		)
	}
}

func hydrateWalletMethod(method *endpointv1.PaymentMethod, field string) (QuoteEndpoint, error) {
	var data pkgmodel.WalletPaymentData
	if err := decodeMethodData(method.GetData(), &data, field); err != nil {
		return QuoteEndpoint{}, err
	}
	ref := strings.TrimSpace(data.WalletID)
	if ref == "" {
		return QuoteEndpoint{}, merrors.InvalidArgument(field + ".wallet_id is required")
	}
	return QuoteEndpoint{
		Type: QuoteEndpointTypeManagedWallet,
		ManagedWallet: &QuoteManagedWalletEndpoint{
			ManagedWalletRef: ref,
		},
	}, nil
}

func hydrateCryptoAddressMethod(method *endpointv1.PaymentMethod, field string) (QuoteEndpoint, error) {
	var data pkgmodel.CryptoAddressPaymentData
	if err := decodeMethodData(method.GetData(), &data, field); err != nil {
		return QuoteEndpoint{}, err
	}
	addr := strings.TrimSpace(data.Address)
	if addr == "" {
		return QuoteEndpoint{}, merrors.InvalidArgument(field + ".address is required")
	}
	asset := &paymenttypes.Asset{
		TokenSymbol: strings.ToUpper(strings.TrimSpace(string(data.Currency))),
	}
	network := chainpkg.NetworkFromString(data.Network)
	if networkAlias := strings.TrimSpace(chainpkg.NetworkAlias(network)); networkAlias != "" && networkAlias != "UNSPECIFIED" {
		asset.Chain = networkAlias
	}
	if data.DestinationTag != nil {
		memo := strings.TrimSpace(*data.DestinationTag)
		return QuoteEndpoint{
			Type: QuoteEndpointTypeExternalChain,
			ExternalChain: &QuoteExternalChainEndpoint{
				Asset:   asset,
				Address: addr,
				Memo:    memo,
			},
		}, nil
	}
	return QuoteEndpoint{
		Type: QuoteEndpointTypeExternalChain,
		ExternalChain: &QuoteExternalChainEndpoint{
			Asset:   asset,
			Address: addr,
		},
	}, nil
}

func hydrateCardMethod(method *endpointv1.PaymentMethod, field string) (QuoteEndpoint, error) {
	var data pkgmodel.CardPaymentData
	if err := decodeMethodData(method.GetData(), &data, field); err != nil {
		return QuoteEndpoint{}, err
	}
	expMonth, err := parseUint32(data.ExpMonth, field+".exp_month")
	if err != nil {
		return QuoteEndpoint{}, err
	}
	expYear, err := parseUint32(data.ExpYear, field+".exp_year")
	if err != nil {
		return QuoteEndpoint{}, err
	}
	return QuoteEndpoint{
		Type: QuoteEndpointTypeCard,
		Card: &QuoteCardEndpoint{
			Pan:               strings.TrimSpace(data.Pan),
			Cardholder:        strings.TrimSpace(data.FirstName),
			CardholderSurname: strings.TrimSpace(data.LastName),
			ExpMonth:          expMonth,
			ExpYear:           expYear,
			Country:           strings.TrimSpace(data.Country),
		},
	}, nil
}

func hydrateCardTokenMethod(method *endpointv1.PaymentMethod, field string) (QuoteEndpoint, error) {
	var data pkgmodel.TokenPaymentData
	if err := decodeMethodData(method.GetData(), &data, field); err != nil {
		return QuoteEndpoint{}, err
	}
	expMonth, err := parseUint32(data.ExpMonth, field+".exp_month")
	if err != nil {
		return QuoteEndpoint{}, err
	}
	expYear, err := parseUint32(data.ExpYear, field+".exp_year")
	if err != nil {
		return QuoteEndpoint{}, err
	}
	return QuoteEndpoint{
		Type: QuoteEndpointTypeCard,
		Card: &QuoteCardEndpoint{
			Token:      strings.TrimSpace(data.Token),
			Cardholder: strings.TrimSpace(data.CardholderName),
			ExpMonth:   expMonth,
			ExpYear:    expYear,
			Country:    strings.TrimSpace(data.Country),
			MaskedPan:  strings.TrimSpace(data.Last4),
		},
	}, nil
}

func hydrateLedgerMethod(method *endpointv1.PaymentMethod, field string) (QuoteEndpoint, error) {
	type ledgerData struct {
		LedgerAccountRef       string `bson:"ledgerAccountRef"`
		ContraLedgerAccountRef string `bson:"contraLedgerAccountRef,omitempty"`
	}
	var data ledgerData
	if err := decodeMethodData(method.GetData(), &data, field); err != nil {
		return QuoteEndpoint{}, err
	}
	ref := strings.TrimSpace(data.LedgerAccountRef)
	if ref == "" {
		return QuoteEndpoint{}, merrors.InvalidArgument(field + ".ledger_account_ref is required")
	}
	return QuoteEndpoint{
		Type: QuoteEndpointTypeLedger,
		Ledger: &QuoteLedgerEndpoint{
			LedgerAccountRef:       ref,
			ContraLedgerAccountRef: strings.TrimSpace(data.ContraLedgerAccountRef),
		},
	}, nil
}
