package opagg

import (
	"fmt"
	"sort"
	"strconv"
	"strings"

	"github.com/tech/sendico/payments/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
)

func endpointKey(ep model.PaymentEndpoint) (string, error) {
	endpointType := normalizeEndpointType(ep)
	if endpointType == model.EndpointTypeUnspecified {
		return "", merrors.InvalidArgument("endpoint type is required")
	}

	parts := []string{
		"type=" + strings.ToLower(strings.TrimSpace(string(endpointType))),
		"instance=" + strings.TrimSpace(ep.InstanceID),
	}

	switch endpointType {
	case model.EndpointTypeLedger:
		if ep.Ledger == nil {
			return "", merrors.InvalidArgument("ledger endpoint is required")
		}
		parts = append(parts,
			"account="+strings.TrimSpace(ep.Ledger.LedgerAccountRef),
			"contra="+strings.TrimSpace(ep.Ledger.ContraLedgerAccountRef),
		)
	case model.EndpointTypeManagedWallet:
		if ep.ManagedWallet == nil {
			return "", merrors.InvalidArgument("managed_wallet endpoint is required")
		}
		parts = append(parts,
			"wallet="+strings.TrimSpace(ep.ManagedWallet.ManagedWalletRef),
			"asset="+assetKey(ep.ManagedWallet.Asset),
		)
	case model.EndpointTypeExternalChain:
		if ep.ExternalChain == nil {
			return "", merrors.InvalidArgument("external_chain endpoint is required")
		}
		parts = append(parts,
			"address="+strings.TrimSpace(ep.ExternalChain.Address),
			"memo="+strings.TrimSpace(ep.ExternalChain.Memo),
			"asset="+assetKey(ep.ExternalChain.Asset),
		)
	case model.EndpointTypeCard:
		if ep.Card == nil {
			return "", merrors.InvalidArgument("card endpoint is required")
		}
		parts = append(parts,
			"token="+strings.TrimSpace(ep.Card.Token),
			"pan="+strings.TrimSpace(ep.Card.Pan),
			"masked="+strings.TrimSpace(ep.Card.MaskedPan),
			"country="+strings.TrimSpace(ep.Card.Country),
			"exp="+strconv.FormatUint(uint64(ep.Card.ExpMonth), 10)+"-"+strconv.FormatUint(uint64(ep.Card.ExpYear), 10),
		)
	default:
		return "", merrors.InvalidArgument("unsupported endpoint type")
	}

	return strings.Join(parts, "|"), nil
}

func normalizeEndpointType(ep model.PaymentEndpoint) model.PaymentEndpointType {
	if ep.Type != model.EndpointTypeUnspecified {
		return ep.Type
	}
	switch {
	case ep.Ledger != nil:
		return model.EndpointTypeLedger
	case ep.ManagedWallet != nil:
		return model.EndpointTypeManagedWallet
	case ep.ExternalChain != nil:
		return model.EndpointTypeExternalChain
	case ep.Card != nil:
		return model.EndpointTypeCard
	default:
		return model.EndpointTypeUnspecified
	}
}

func routeSignature(route *paymenttypes.QuoteRouteSpecification) string {
	if route == nil {
		return "none"
	}
	parts := []string{
		"route_ref=" + strings.TrimSpace(route.RouteRef),
		"rail=" + strings.ToUpper(strings.TrimSpace(route.Rail)),
		"provider=" + strings.TrimSpace(route.Provider),
		"network=" + strings.TrimSpace(route.Network),
		"settlement=" + settlementKey(route.Settlement),
	}
	for i, hop := range route.Hops {
		if hop == nil {
			continue
		}
		parts = append(parts, fmt.Sprintf(
			"hop[%d]=%d:%s:%s:%s:%s:%s",
			i,
			hop.Index,
			strings.ToUpper(strings.TrimSpace(hop.Rail)),
			strings.TrimSpace(hop.Gateway),
			strings.TrimSpace(hop.InstanceID),
			strings.TrimSpace(hop.Network),
			strings.ToUpper(strings.TrimSpace(string(hop.Role))),
		))
	}
	return strings.Join(parts, "|")
}

func settlementKey(s *paymenttypes.QuoteRouteSettlement) string {
	if s == nil {
		return "none"
	}
	return strings.Join([]string{
		"model=" + strings.TrimSpace(s.Model),
		"asset=" + assetKey(s.Asset),
	}, "|")
}

func fxQuoteSignature(q *paymenttypes.FXQuote) string {
	if q == nil {
		return "none"
	}
	pair := "none"
	if q.Pair != nil {
		pair = strings.ToUpper(strings.TrimSpace(q.Pair.Base)) + "/" + strings.ToUpper(strings.TrimSpace(q.Pair.Quote))
	}
	price := ""
	if q.Price != nil {
		price = strings.TrimSpace(q.Price.Value)
	}
	return strings.Join([]string{
		"pair=" + pair,
		"side=" + strings.ToUpper(strings.TrimSpace(string(q.Side))),
		"price=" + price,
		"provider=" + strings.TrimSpace(q.Provider),
		"rate_ref=" + strings.TrimSpace(q.RateRef),
		"firm=" + strconv.FormatBool(q.Firm),
	}, "|")
}

func feeLineKey(line *paymenttypes.FeeLine) string {
	if line == nil {
		return ""
	}
	return strings.Join([]string{
		strings.TrimSpace(line.LedgerAccountRef),
		strings.ToUpper(strings.TrimSpace(string(line.LineType))),
		strings.ToUpper(strings.TrimSpace(string(line.Side))),
		moneyCurrency(line.Money),
		metadataSignature(line.Meta),
	}, "|")
}

func feeRuleKey(rule *paymenttypes.AppliedRule) string {
	if rule == nil {
		return ""
	}
	return strings.Join([]string{
		strings.TrimSpace(rule.RuleID),
		strings.TrimSpace(rule.RuleVersion),
		strings.TrimSpace(rule.Formula),
		strings.TrimSpace(rule.TaxCode),
		strings.TrimSpace(rule.TaxRate),
		metadataSignature(rule.Parameters),
	}, "|")
}

func metadataSignature(meta map[string]string) string {
	if len(meta) == 0 {
		return ""
	}
	keys := make([]string, 0, len(meta))
	for key := range meta {
		k := strings.TrimSpace(key)
		if k == "" {
			continue
		}
		keys = append(keys, k)
	}
	sort.Strings(keys)
	if len(keys) == 0 {
		return ""
	}
	parts := make([]string, 0, len(keys))
	for _, key := range keys {
		parts = append(parts, key+"="+strings.TrimSpace(meta[key]))
	}
	return strings.Join(parts, ",")
}

func moneyCurrency(m *paymenttypes.Money) string {
	if m == nil {
		return ""
	}
	return normalizeCurrency(m.Currency)
}

func normalizeCurrency(value string) string {
	return strings.ToUpper(strings.TrimSpace(value))
}

func assetKey(asset *paymenttypes.Asset) string {
	if asset == nil {
		return ""
	}
	return strings.Join([]string{
		strings.ToUpper(strings.TrimSpace(asset.Chain)),
		strings.ToUpper(strings.TrimSpace(asset.TokenSymbol)),
		strings.TrimSpace(asset.ContractAddress),
	}, ":")
}
