package opagg

import (
	"strings"

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

func mergeRoute(dst, src *paymenttypes.QuoteRouteSpecification) (*paymenttypes.QuoteRouteSpecification, error) {
	if dst == nil {
		return cloneRoute(src), nil
	}
	if src == nil {
		return dst, nil
	}
	// Aggregation is destination-driven; keep the first route as execution template.
	return dst, nil
}

func mergeFXQuote(dst, src *paymenttypes.FXQuote) (*paymenttypes.FXQuote, error) {
	if dst == nil {
		return cloneFXQuote(src), nil
	}
	if src == nil {
		return dst, nil
	}
	var err error
	dst.BaseAmount, err = mergeMoney(dst.BaseAmount, src.BaseAmount, "quote_snapshot.fx_quote.base_amount")
	if err != nil {
		return nil, err
	}
	dst.QuoteAmount, err = mergeMoney(dst.QuoteAmount, src.QuoteAmount, "quote_snapshot.fx_quote.quote_amount")
	if err != nil {
		return nil, err
	}
	if dst.ExpiresAtUnixMs == 0 || (src.ExpiresAtUnixMs > 0 && src.ExpiresAtUnixMs < dst.ExpiresAtUnixMs) {
		dst.ExpiresAtUnixMs = src.ExpiresAtUnixMs
	}
	if src.PricedAtUnixMs > dst.PricedAtUnixMs {
		dst.PricedAtUnixMs = src.PricedAtUnixMs
	}
	if dst.QuoteRef == "" {
		dst.QuoteRef = src.QuoteRef
	}
	return dst, nil
}

func mergeExecutionConditions(dst, src *paymenttypes.QuoteExecutionConditions) *paymenttypes.QuoteExecutionConditions {
	if dst == nil {
		return cloneExecutionConditions(src)
	}
	if src == nil {
		return dst
	}

	dst.Readiness = mergedReadiness(dst.Readiness, src.Readiness)
	dst.BatchingEligible = dst.BatchingEligible && src.BatchingEligible
	dst.PrefundingRequired = dst.PrefundingRequired || src.PrefundingRequired
	dst.PrefundingCostIncluded = dst.PrefundingCostIncluded || src.PrefundingCostIncluded
	dst.LiquidityCheckRequiredAtExecution = dst.LiquidityCheckRequiredAtExecution || src.LiquidityCheckRequiredAtExecution
	if dst.LatencyHint == "" {
		dst.LatencyHint = src.LatencyHint
	} else if srcHint := strings.TrimSpace(src.LatencyHint); srcHint != "" && !strings.EqualFold(dst.LatencyHint, srcHint) {
		dst.LatencyHint = "mixed"
	}
	dst.Assumptions = mergeAssumptions(dst.Assumptions, src.Assumptions)

	return dst
}

func mergedReadiness(a, b paymenttypes.QuoteExecutionReadiness) paymenttypes.QuoteExecutionReadiness {
	scoreA := readinessScore(a)
	scoreB := readinessScore(b)
	if scoreA <= scoreB {
		return a
	}
	return b
}

func readinessScore(v paymenttypes.QuoteExecutionReadiness) int {
	switch v {
	case paymenttypes.QuoteExecutionReadinessIndicative:
		return 0
	case paymenttypes.QuoteExecutionReadinessLiquidityObtainable:
		return 1
	case paymenttypes.QuoteExecutionReadinessLiquidityReady:
		return 2
	default:
		return 2
	}
}

func mergeAssumptions(dst, src []string) []string {
	if len(dst) == 0 && len(src) == 0 {
		return nil
	}
	seen := make(map[string]struct{}, len(dst)+len(src))
	out := make([]string, 0, len(dst)+len(src))
	for _, item := range dst {
		key := strings.TrimSpace(item)
		if key == "" {
			continue
		}
		if _, exists := seen[key]; exists {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, key)
	}
	for _, item := range src {
		key := strings.TrimSpace(item)
		if key == "" {
			continue
		}
		if _, exists := seen[key]; exists {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, key)
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func mergeNetworkFee(dst, src *paymenttypes.NetworkFeeEstimate) (*paymenttypes.NetworkFeeEstimate, error) {
	if dst == nil {
		return cloneNetworkFee(src), nil
	}
	if src == nil {
		return dst, nil
	}
	sum, err := mergeMoney(dst.NetworkFee, src.NetworkFee, "quote_snapshot.network_fee.network_fee")
	if err != nil {
		return nil, err
	}
	dst.NetworkFee = sum
	if dst.EstimationContext == "" {
		dst.EstimationContext = strings.TrimSpace(src.EstimationContext)
	} else if ctx := strings.TrimSpace(src.EstimationContext); ctx != "" && !strings.EqualFold(dst.EstimationContext, ctx) {
		dst.EstimationContext = "mixed"
	}
	return dst, nil
}

func mergeFeeLines(dst, src []*paymenttypes.FeeLine) ([]*paymenttypes.FeeLine, error) {
	if len(dst) == 0 {
		return cloneFeeLines(src), nil
	}
	if len(src) == 0 {
		return dst, nil
	}

	out := cloneFeeLines(dst)
	indexByKey := make(map[string]int, len(out))
	for i, line := range out {
		if line == nil {
			continue
		}
		indexByKey[feeLineKey(line)] = i
	}

	for _, line := range src {
		if line == nil {
			continue
		}
		key := feeLineKey(line)
		if idx, exists := indexByKey[key]; exists {
			sum, err := mergeMoney(out[idx].Money, line.Money, "quote_snapshot.fee_lines["+key+"]")
			if err != nil {
				return nil, err
			}
			out[idx].Money = sum
			continue
		}
		cloned := cloneFeeLine(line)
		indexByKey[key] = len(out)
		out = append(out, cloned)
	}

	return out, nil
}

func mergeFeeRules(dst, src []*paymenttypes.AppliedRule) []*paymenttypes.AppliedRule {
	if len(dst) == 0 {
		return cloneFeeRules(src)
	}
	if len(src) == 0 {
		return dst
	}

	out := cloneFeeRules(dst)
	seen := make(map[string]struct{}, len(out))
	for _, rule := range out {
		if rule == nil {
			continue
		}
		seen[feeRuleKey(rule)] = struct{}{}
	}
	for _, rule := range src {
		if rule == nil {
			continue
		}
		key := feeRuleKey(rule)
		if _, exists := seen[key]; exists {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, cloneFeeRule(rule))
	}
	return out
}
