package paymentapiimp

import (
	"strings"

	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/model"
	fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
	"github.com/tech/sendico/server/interface/api/srequest"
)

func mapPaymentIntent(intent *srequest.PaymentIntent) (*orchestratorv1.PaymentIntent, error) {
	if intent == nil {
		return nil, merrors.InvalidArgument("intent is required")
	}

	kind, err := mapPaymentKind(intent.Kind)
	if err != nil {
		return nil, err
	}

	settlementMode, err := mapSettlementMode(intent.SettlementMode)
	if err != nil {
		return nil, err
	}
	settlementCurrency := strings.TrimSpace(intent.SettlementCurrency)
	if settlementCurrency == "" {
		settlementCurrency = resolveSettlementCurrency(intent)
	}

	source, err := mapPaymentEndpoint(intent.Source, "source")
	if err != nil {
		return nil, err
	}
	destination, err := mapPaymentEndpoint(intent.Destination, "destination")
	if err != nil {
		return nil, err
	}

	fx, err := mapFXIntent(intent.FX)
	if err != nil {
		return nil, err
	}

	return &orchestratorv1.PaymentIntent{
		Kind:               kind,
		Source:             source,
		Destination:        destination,
		Amount:             mapMoney(intent.Amount),
		RequiresFx:         fx != nil,
		Fx:                 fx,
		SettlementMode:     settlementMode,
		SettlementCurrency: settlementCurrency,
		Attributes:         copyStringMap(intent.Attributes),
		Customer:           mapCustomer(intent.Customer),
	}, nil
}

func resolveSettlementCurrency(intent *srequest.PaymentIntent) string {
	if intent == nil {
		return ""
	}

	fx := intent.FX
	if fx != nil && fx.Pair != nil {
		base := strings.TrimSpace(fx.Pair.Base)
		quote := strings.TrimSpace(fx.Pair.Quote)
		switch strings.TrimSpace(string(fx.Side)) {
		case string(srequest.FXSideBuyBaseSellQuote):
			if base != "" {
				return base
			}
		case string(srequest.FXSideSellBaseBuyQuote):
			if quote != "" {
				return quote
			}
		}
		if intent.Amount != nil {
			amountCurrency := strings.TrimSpace(intent.Amount.Currency)
			if amountCurrency != "" {
				switch {
				case strings.EqualFold(amountCurrency, base) && quote != "":
					return quote
				case strings.EqualFold(amountCurrency, quote) && base != "":
					return base
				default:
					return amountCurrency
				}
			}
		}
		if quote != "" {
			return quote
		}
		if base != "" {
			return base
		}
	}

	if intent.Amount != nil {
		return strings.TrimSpace(intent.Amount.Currency)
	}

	return ""
}

func mapPaymentEndpoint(endpoint *srequest.Endpoint, field string) (*orchestratorv1.PaymentEndpoint, error) {
	if endpoint == nil {
		return nil, nil
	}

	var result orchestratorv1.PaymentEndpoint
	switch endpoint.Type {
	case srequest.EndpointTypeLedger:
		payload, err := endpoint.DecodeLedger()
		if err != nil {
			return nil, merrors.InvalidArgument(field + " endpoint: " + err.Error())
		}
		result.Endpoint = &orchestratorv1.PaymentEndpoint_Ledger{
			Ledger: mapLedgerEndpoint(&payload),
		}
	case srequest.EndpointTypeManagedWallet:
		payload, err := endpoint.DecodeManagedWallet()
		if err != nil {
			return nil, merrors.InvalidArgument(field + " endpoint: " + err.Error())
		}
		mw, err := mapManagedWalletEndpoint(&payload)
		if err != nil {
			return nil, merrors.InvalidArgument(field + " endpoint: " + err.Error())
		}
		result.Endpoint = &orchestratorv1.PaymentEndpoint_ManagedWallet{
			ManagedWallet: mw,
		}
	case srequest.EndpointTypeExternalChain:
		payload, err := endpoint.DecodeExternalChain()
		if err != nil {
			return nil, merrors.InvalidArgument(field + " endpoint: " + err.Error())
		}
		ext, err := mapExternalChainEndpoint(&payload)
		if err != nil {
			return nil, merrors.InvalidArgument(field + " endpoint: " + err.Error())
		}
		result.Endpoint = &orchestratorv1.PaymentEndpoint_ExternalChain{
			ExternalChain: ext,
		}
	case srequest.EndpointTypeCard:
		payload, err := endpoint.DecodeCard()
		if err != nil {
			return nil, merrors.InvalidArgument(field + " endpoint: " + err.Error())
		}
		result.Endpoint = &orchestratorv1.PaymentEndpoint_Card{
			Card: mapCardEndpoint(&payload),
		}
	case srequest.EndpointTypeCardToken:
		payload, err := endpoint.DecodeCardToken()
		if err != nil {
			return nil, merrors.InvalidArgument(field + " endpoint: " + err.Error())
		}
		result.Endpoint = &orchestratorv1.PaymentEndpoint_Card{
			Card: mapCardTokenEndpoint(&payload),
		}
	case "":
		return nil, merrors.InvalidArgument(field + " endpoint type is required")
	default:
		return nil, merrors.InvalidArgument(field + " endpoint has unsupported type: " + string(endpoint.Type))
	}

	result.Metadata = copyStringMap(endpoint.Metadata)
	return &result, nil
}

func mapLedgerEndpoint(endpoint *srequest.LedgerEndpoint) *orchestratorv1.LedgerEndpoint {
	if endpoint == nil {
		return nil
	}
	return &orchestratorv1.LedgerEndpoint{
		LedgerAccountRef:       endpoint.LedgerAccountRef,
		ContraLedgerAccountRef: endpoint.ContraLedgerAccountRef,
	}
}

func mapManagedWalletEndpoint(endpoint *srequest.ManagedWalletEndpoint) (*orchestratorv1.ManagedWalletEndpoint, error) {
	if endpoint == nil {
		return nil, nil
	}
	asset, err := mapAsset(endpoint.Asset)
	if err != nil {
		return nil, err
	}
	return &orchestratorv1.ManagedWalletEndpoint{
		ManagedWalletRef: endpoint.ManagedWalletRef,
		Asset:            asset,
	}, nil
}

func mapExternalChainEndpoint(endpoint *srequest.ExternalChainEndpoint) (*orchestratorv1.ExternalChainEndpoint, error) {
	if endpoint == nil {
		return nil, nil
	}
	asset, err := mapAsset(endpoint.Asset)
	if err != nil {
		return nil, err
	}
	return &orchestratorv1.ExternalChainEndpoint{
		Asset:   asset,
		Address: endpoint.Address,
		Memo:    endpoint.Memo,
	}, nil
}

func mapAsset(asset *srequest.Asset) (*chainv1.Asset, error) {
	if asset == nil {
		return nil, nil
	}
	chain, err := mapChainNetwork(asset.Chain)
	if err != nil {
		return nil, err
	}
	return &chainv1.Asset{
		Chain:           chain,
		TokenSymbol:     asset.TokenSymbol,
		ContractAddress: asset.ContractAddress,
	}, nil
}

func mapMoney(m *model.Money) *moneyv1.Money {
	if m == nil {
		return nil
	}
	return &moneyv1.Money{
		Amount:   m.Amount,
		Currency: m.Currency,
	}
}

func mapFXIntent(fx *srequest.FXIntent) (*orchestratorv1.FXIntent, error) {
	if fx == nil {
		return nil, nil
	}
	side, err := mapFXSide(fx.Side)
	if err != nil {
		return nil, err
	}
	return &orchestratorv1.FXIntent{
		Pair:              mapCurrencyPair(fx.Pair),
		Side:              side,
		Firm:              fx.Firm,
		TtlMs:             fx.TTLms,
		PreferredProvider: fx.PreferredProvider,
		MaxAgeMs:          fx.MaxAgeMs,
	}, nil
}

func mapCustomer(customer *srequest.Customer) *orchestratorv1.Customer {
	if customer == nil {
		return nil
	}
	return &orchestratorv1.Customer{
		Id:         strings.TrimSpace(customer.ID),
		FirstName:  strings.TrimSpace(customer.FirstName),
		MiddleName: strings.TrimSpace(customer.MiddleName),
		LastName:   strings.TrimSpace(customer.LastName),
		Ip:         strings.TrimSpace(customer.IP),
		Zip:        strings.TrimSpace(customer.Zip),
		Country:    strings.TrimSpace(customer.Country),
		State:      strings.TrimSpace(customer.State),
		City:       strings.TrimSpace(customer.City),
		Address:    strings.TrimSpace(customer.Address),
	}
}

func mapCurrencyPair(pair *srequest.CurrencyPair) *fxv1.CurrencyPair {
	if pair == nil {
		return nil
	}
	return &fxv1.CurrencyPair{
		Base:  pair.Base,
		Quote: pair.Quote,
	}
}

func mapCardEndpoint(card *srequest.CardEndpoint) *orchestratorv1.CardEndpoint {
	if card == nil {
		return nil
	}
	result := &orchestratorv1.CardEndpoint{
		CardholderName:    strings.TrimSpace(card.FirstName),
		CardholderSurname: strings.TrimSpace(card.LastName),
		ExpMonth:          card.ExpMonth,
		ExpYear:           card.ExpYear,
		Country:           strings.TrimSpace(card.Country),
	}
	if pan := strings.TrimSpace(card.Pan); pan != "" {
		result.Card = &orchestratorv1.CardEndpoint_Pan{Pan: pan}
	}
	return result
}

func mapCardTokenEndpoint(card *srequest.CardTokenEndpoint) *orchestratorv1.CardEndpoint {
	if card == nil {
		return nil
	}
	return &orchestratorv1.CardEndpoint{
		Card:      &orchestratorv1.CardEndpoint_Token{Token: strings.TrimSpace(card.Token)},
		MaskedPan: strings.TrimSpace(card.MaskedPan),
	}
}

func mapPaymentKind(kind srequest.PaymentKind) (orchestratorv1.PaymentKind, error) {
	switch strings.TrimSpace(string(kind)) {
	case "", string(srequest.PaymentKindUnspecified):
		return orchestratorv1.PaymentKind_PAYMENT_KIND_UNSPECIFIED, nil
	case string(srequest.PaymentKindPayout):
		return orchestratorv1.PaymentKind_PAYMENT_KIND_PAYOUT, nil
	case string(srequest.PaymentKindInternalTransfer):
		return orchestratorv1.PaymentKind_PAYMENT_KIND_INTERNAL_TRANSFER, nil
	case string(srequest.PaymentKindFxConversion):
		return orchestratorv1.PaymentKind_PAYMENT_KIND_FX_CONVERSION, nil
	default:
		return orchestratorv1.PaymentKind_PAYMENT_KIND_UNSPECIFIED, merrors.InvalidArgument("unsupported payment kind: " + string(kind))
	}
}

func mapSettlementMode(mode srequest.SettlementMode) (orchestratorv1.SettlementMode, error) {
	switch strings.TrimSpace(string(mode)) {
	case "", string(srequest.SettlementModeUnspecified):
		return orchestratorv1.SettlementMode_SETTLEMENT_UNSPECIFIED, nil
	case string(srequest.SettlementModeFixSource):
		return orchestratorv1.SettlementMode_SETTLEMENT_FIX_SOURCE, nil
	case string(srequest.SettlementModeFixReceived):
		return orchestratorv1.SettlementMode_SETTLEMENT_FIX_RECEIVED, nil
	default:
		return orchestratorv1.SettlementMode_SETTLEMENT_UNSPECIFIED, merrors.InvalidArgument("unsupported settlement mode: " + string(mode))
	}
}

func mapFXSide(side srequest.FXSide) (fxv1.Side, error) {
	switch strings.TrimSpace(string(side)) {
	case "", string(srequest.FXSideUnspecified):
		return fxv1.Side_SIDE_UNSPECIFIED, nil
	case string(srequest.FXSideBuyBaseSellQuote):
		return fxv1.Side_BUY_BASE_SELL_QUOTE, nil
	case string(srequest.FXSideSellBaseBuyQuote):
		return fxv1.Side_SELL_BASE_BUY_QUOTE, nil
	default:
		return fxv1.Side_SIDE_UNSPECIFIED, merrors.InvalidArgument("unsupported fx side: " + string(side))
	}
}

func mapChainNetwork(chain srequest.ChainNetwork) (chainv1.ChainNetwork, error) {
	switch strings.TrimSpace(string(chain)) {
	case "", string(srequest.ChainNetworkUnspecified):
		return chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED, nil
	case string(srequest.ChainNetworkEthereumMainnet):
		return chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET, nil
	case string(srequest.ChainNetworkArbitrumOne):
		return chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE, nil
	case string(srequest.ChainNetworkTronMainnet):
		return chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET, nil
	case string(srequest.ChainNetworkTronNile):
		return chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE, nil
	default:
		return chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED, merrors.InvalidArgument("unsupported chain network: " + string(chain))
	}
}

func copyStringMap(src map[string]string) map[string]string {
	if len(src) == 0 {
		return nil
	}
	dst := make(map[string]string, len(src))
	for k, v := range src {
		dst[k] = v
	}
	return dst
}
