package quote_computation_service

import (
	"strings"

	"github.com/shopspring/decimal"
	feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
	accountingv1 "github.com/tech/sendico/pkg/proto/common/accounting/v1"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1"
	quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
)

func ensureComputedQuote(src *ComputedQuote, item *QuoteComputationPlanItem) *ComputedQuote {
	if src == nil {
		src = &ComputedQuote{}
	}
	if item == nil {
		return src
	}
	if src.Route == nil {
		src.Route = cloneRouteSpecification(item.Route)
	}
	if src.ExecutionConditions == nil {
		src.ExecutionConditions = cloneExecutionConditions(item.ExecutionConditions)
	}
	if src.TotalCost == nil {
		src.TotalCost = deriveTotalCost(src.DebitAmount, src.FeeLines)
	}
	if src.ResolvedSettlementMode == paymentv1.SettlementMode_SETTLEMENT_UNSPECIFIED {
		src.ResolvedSettlementMode = item.ResolvedSettlementMode
	}
	if src.ResolvedSettlementMode == paymentv1.SettlementMode_SETTLEMENT_UNSPECIFIED {
		src.ResolvedSettlementMode = paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE
	}
	if src.ResolvedFeeTreatment == quotationv2.FeeTreatment_FEE_TREATMENT_UNSPECIFIED {
		src.ResolvedFeeTreatment = item.ResolvedFeeTreatment
	}
	if src.ResolvedFeeTreatment == quotationv2.FeeTreatment_FEE_TREATMENT_UNSPECIFIED {
		src.ResolvedFeeTreatment = defaultResolvedFeeTreatment()
	}
	return src
}

func deriveTotalCost(
	debitAmount *moneyv1.Money,
	feeLines []*feesv1.DerivedPostingLine,
) *moneyv1.Money {
	if debitAmount == nil {
		return nil
	}
	currency := strings.ToUpper(strings.TrimSpace(debitAmount.GetCurrency()))
	baseValue, err := decimal.NewFromString(strings.TrimSpace(debitAmount.GetAmount()))
	if err != nil {
		return nil
	}

	total := baseValue
	for _, line := range feeLines {
		if line == nil || line.GetMoney() == nil {
			continue
		}
		lineCurrency := strings.ToUpper(strings.TrimSpace(line.GetMoney().GetCurrency()))
		if lineCurrency == "" || lineCurrency != currency {
			continue
		}
		lineAmount, convErr := decimal.NewFromString(strings.TrimSpace(line.GetMoney().GetAmount()))
		if convErr != nil {
			continue
		}
		switch line.GetSide() {
		case accountingv1.EntrySide_ENTRY_SIDE_DEBIT:
			total = total.Add(lineAmount)
		case accountingv1.EntrySide_ENTRY_SIDE_CREDIT:
			total = total.Sub(lineAmount)
		}
	}
	return &moneyv1.Money{
		Amount:   total.String(),
		Currency: currency,
	}
}

func cloneRouteSpecification(src *quotationv2.RouteSpecification) *quotationv2.RouteSpecification {
	if src == nil {
		return nil
	}
	result := &quotationv2.RouteSpecification{
		Rail:              strings.TrimSpace(src.GetRail()),
		Provider:          strings.TrimSpace(src.GetProvider()),
		PayoutMethod:      strings.TrimSpace(src.GetPayoutMethod()),
		Network:           strings.TrimSpace(src.GetNetwork()),
		RouteRef:          strings.TrimSpace(src.GetRouteRef()),
		PricingProfileRef: strings.TrimSpace(src.GetPricingProfileRef()),
		Settlement:        cloneRouteSettlement(src.GetSettlement()),
	}
	if hops := src.GetHops(); len(hops) > 0 {
		result.Hops = make([]*quotationv2.RouteHop, 0, len(hops))
		for _, hop := range hops {
			if cloned := cloneRouteHop(hop); cloned != nil {
				result.Hops = append(result.Hops, cloned)
			}
		}
		if len(result.Hops) == 0 {
			result.Hops = nil
		}
	}
	return result
}

func cloneRouteSettlement(src *quotationv2.RouteSettlement) *quotationv2.RouteSettlement {
	if src == nil {
		return nil
	}
	result := &quotationv2.RouteSettlement{
		Model: strings.TrimSpace(src.GetModel()),
	}
	if asset := src.GetAsset(); asset != nil {
		key := asset.GetKey()
		result.Asset = &paymentv1.ChainAsset{
			Key: &paymentv1.ChainAssetKey{
				Chain:       strings.ToUpper(strings.TrimSpace(key.GetChain())),
				TokenSymbol: strings.ToUpper(strings.TrimSpace(key.GetTokenSymbol())),
			},
		}
		if contract := strings.TrimSpace(asset.GetContractAddress()); contract != "" {
			result.Asset.ContractAddress = &contract
		}
	}
	if result.Asset == nil && result.Model == "" {
		return nil
	}
	return result
}

func cloneExecutionConditions(src *quotationv2.ExecutionConditions) *quotationv2.ExecutionConditions {
	if src == nil {
		return nil
	}
	result := &quotationv2.ExecutionConditions{
		Readiness:                         src.GetReadiness(),
		BatchingEligible:                  src.GetBatchingEligible(),
		PrefundingRequired:                src.GetPrefundingRequired(),
		PrefundingCostIncluded:            src.GetPrefundingCostIncluded(),
		LiquidityCheckRequiredAtExecution: src.GetLiquidityCheckRequiredAtExecution(),
		LatencyHint:                       strings.TrimSpace(src.GetLatencyHint()),
	}
	for _, assumption := range src.GetAssumptions() {
		value := strings.TrimSpace(assumption)
		if value == "" {
			continue
		}
		result.Assumptions = append(result.Assumptions, value)
	}
	if len(result.Assumptions) == 0 {
		result.Assumptions = nil
	}
	return result
}

func cloneRouteHop(src *quotationv2.RouteHop) *quotationv2.RouteHop {
	if src == nil {
		return nil
	}
	return &quotationv2.RouteHop{
		Index:      src.GetIndex(),
		Rail:       strings.TrimSpace(src.GetRail()),
		Gateway:    strings.TrimSpace(src.GetGateway()),
		InstanceId: strings.TrimSpace(src.GetInstanceId()),
		Network:    strings.TrimSpace(src.GetNetwork()),
		Role:       src.GetRole(),
	}
}
