package opagg

import (
	"strings"

	"github.com/tech/sendico/payments/storage/model"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	"go.mongodb.org/mongo-driver/v2/bson"
)

func cloneMoney(src *paymenttypes.Money) *paymenttypes.Money {
	if src == nil {
		return nil
	}
	return &paymenttypes.Money{
		Amount:   strings.TrimSpace(src.Amount),
		Currency: normalizeCurrency(src.Currency),
	}
}

func cloneNetworkFee(src *paymenttypes.NetworkFeeEstimate) *paymenttypes.NetworkFeeEstimate {
	if src == nil {
		return nil
	}
	return &paymenttypes.NetworkFeeEstimate{
		NetworkFee:        cloneMoney(src.NetworkFee),
		EstimationContext: strings.TrimSpace(src.EstimationContext),
	}
}

func cloneFeeLines(src []*paymenttypes.FeeLine) []*paymenttypes.FeeLine {
	if len(src) == 0 {
		return nil
	}
	out := make([]*paymenttypes.FeeLine, 0, len(src))
	for _, line := range src {
		if line == nil {
			continue
		}
		out = append(out, cloneFeeLine(line))
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func cloneFeeLine(src *paymenttypes.FeeLine) *paymenttypes.FeeLine {
	if src == nil {
		return nil
	}
	return &paymenttypes.FeeLine{
		LedgerAccountRef: strings.TrimSpace(src.LedgerAccountRef),
		Money:            cloneMoney(src.Money),
		LineType:         src.LineType,
		Side:             src.Side,
		Meta:             cloneMetadata(src.Meta),
	}
}

func cloneFeeRules(src []*paymenttypes.AppliedRule) []*paymenttypes.AppliedRule {
	if len(src) == 0 {
		return nil
	}
	out := make([]*paymenttypes.AppliedRule, 0, len(src))
	for _, rule := range src {
		if rule == nil {
			continue
		}
		out = append(out, cloneFeeRule(rule))
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func cloneFeeRule(src *paymenttypes.AppliedRule) *paymenttypes.AppliedRule {
	if src == nil {
		return nil
	}
	return &paymenttypes.AppliedRule{
		RuleID:      strings.TrimSpace(src.RuleID),
		RuleVersion: strings.TrimSpace(src.RuleVersion),
		Formula:     strings.TrimSpace(src.Formula),
		Rounding:    src.Rounding,
		TaxCode:     strings.TrimSpace(src.TaxCode),
		TaxRate:     strings.TrimSpace(src.TaxRate),
		Parameters:  cloneMetadata(src.Parameters),
	}
}

func cloneFXQuote(src *paymenttypes.FXQuote) *paymenttypes.FXQuote {
	if src == nil {
		return nil
	}
	dst := &paymenttypes.FXQuote{
		QuoteRef:        strings.TrimSpace(src.QuoteRef),
		Side:            src.Side,
		ExpiresAtUnixMs: src.ExpiresAtUnixMs,
		PricedAtUnixMs:  src.PricedAtUnixMs,
		Provider:        strings.TrimSpace(src.Provider),
		RateRef:         strings.TrimSpace(src.RateRef),
		Firm:            src.Firm,
		BaseAmount:      cloneMoney(src.BaseAmount),
		QuoteAmount:     cloneMoney(src.QuoteAmount),
	}
	if src.Pair != nil {
		dst.Pair = &paymenttypes.CurrencyPair{
			Base:  normalizeCurrency(src.Pair.Base),
			Quote: normalizeCurrency(src.Pair.Quote),
		}
	}
	if src.Price != nil {
		dst.Price = &paymenttypes.Decimal{Value: strings.TrimSpace(src.Price.Value)}
	}
	return dst
}

func cloneRoute(src *paymenttypes.QuoteRouteSpecification) *paymenttypes.QuoteRouteSpecification {
	if src == nil {
		return nil
	}
	dst := &paymenttypes.QuoteRouteSpecification{
		Rail:              strings.TrimSpace(src.Rail),
		Provider:          strings.TrimSpace(src.Provider),
		PayoutMethod:      strings.TrimSpace(src.PayoutMethod),
		Network:           strings.TrimSpace(src.Network),
		RouteRef:          strings.TrimSpace(src.RouteRef),
		PricingProfileRef: strings.TrimSpace(src.PricingProfileRef),
	}
	if src.Settlement != nil {
		dst.Settlement = &paymenttypes.QuoteRouteSettlement{
			Model: strings.TrimSpace(src.Settlement.Model),
			Asset: cloneAsset(src.Settlement.Asset),
		}
	}
	if len(src.Hops) > 0 {
		dst.Hops = make([]*paymenttypes.QuoteRouteHop, 0, len(src.Hops))
		for _, hop := range src.Hops {
			if hop == nil {
				continue
			}
			dst.Hops = append(dst.Hops, &paymenttypes.QuoteRouteHop{
				Index:      hop.Index,
				Rail:       strings.TrimSpace(hop.Rail),
				Gateway:    strings.TrimSpace(hop.Gateway),
				InstanceID: strings.TrimSpace(hop.InstanceID),
				Network:    strings.TrimSpace(hop.Network),
				Role:       hop.Role,
			})
		}
	}
	return dst
}

func cloneAsset(src *paymenttypes.Asset) *paymenttypes.Asset {
	if src == nil {
		return nil
	}
	return &paymenttypes.Asset{
		Chain:           strings.TrimSpace(src.Chain),
		TokenSymbol:     strings.TrimSpace(src.TokenSymbol),
		ContractAddress: strings.TrimSpace(src.ContractAddress),
	}
}

func cloneExecutionConditions(src *paymenttypes.QuoteExecutionConditions) *paymenttypes.QuoteExecutionConditions {
	if src == nil {
		return nil
	}
	dst := &paymenttypes.QuoteExecutionConditions{
		Readiness:                         src.Readiness,
		BatchingEligible:                  src.BatchingEligible,
		PrefundingRequired:                src.PrefundingRequired,
		PrefundingCostIncluded:            src.PrefundingCostIncluded,
		LiquidityCheckRequiredAtExecution: src.LiquidityCheckRequiredAtExecution,
		LatencyHint:                       strings.TrimSpace(src.LatencyHint),
	}
	if len(src.Assumptions) > 0 {
		dst.Assumptions = cloneStringSlice(src.Assumptions)
	}
	return dst
}

func cloneMetadata(src map[string]string) map[string]string {
	if len(src) == 0 {
		return nil
	}
	out := make(map[string]string, len(src))
	for key, value := range src {
		k := strings.TrimSpace(key)
		if k == "" {
			continue
		}
		out[k] = strings.TrimSpace(value)
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func cloneStringSlice(src []string) []string {
	if len(src) == 0 {
		return nil
	}
	out := make([]string, 0, len(src))
	for _, item := range src {
		token := strings.TrimSpace(item)
		if token == "" {
			continue
		}
		out = append(out, token)
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func cloneGroupMembers(src []GroupMember) []GroupMember {
	if len(src) == 0 {
		return nil
	}
	out := make([]GroupMember, 0, len(src))
	for i := range src {
		member := src[i]
		intentSnapshot, err := cloneIntentSnapshot(member.IntentSnapshot)
		if err != nil {
			continue
		}
		quoteSnapshot, err := cloneQuoteSnapshot(member.QuoteSnapshot)
		if err != nil {
			continue
		}
		out = append(out, GroupMember{
			IntentRef:      strings.TrimSpace(member.IntentRef),
			IntentSnapshot: intentSnapshot,
			QuoteSnapshot:  quoteSnapshot,
		})
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func cloneIntentSnapshot(src model.PaymentIntent) (model.PaymentIntent, error) {
	var dst model.PaymentIntent
	if err := bsonClone(src, &dst); err != nil {
		return model.PaymentIntent{}, err
	}
	return dst, nil
}

func cloneQuoteSnapshot(src *model.PaymentQuoteSnapshot) (*model.PaymentQuoteSnapshot, error) {
	if src == nil {
		return nil, nil //nolint:nilnil // nil snapshot means quote data is intentionally absent
	}
	dst := &model.PaymentQuoteSnapshot{}
	if err := bsonClone(src, dst); err != nil {
		return nil, err
	}
	return dst, nil
}

func bsonClone(src any, dst any) error {
	data, err := bson.Marshal(src)
	if err != nil {
		return err
	}
	return bson.Unmarshal(data, dst)
}
