package orchestrator

import (
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/shopspring/decimal"
	"github.com/tech/sendico/payments/orchestrator/storage/model"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
)

func perIntentIdempotencyKey(base string, index int, total int) string {
	base = strings.TrimSpace(base)
	if base == "" {
		return ""
	}
	if total <= 1 {
		return base
	}
	return fmt.Sprintf("%s:%d", base, index+1)
}

func minQuoteExpiry(expires []time.Time) (time.Time, bool) {
	var min time.Time
	for _, exp := range expires {
		if exp.IsZero() {
			continue
		}
		if min.IsZero() || exp.Before(min) {
			min = exp
		}
	}
	if min.IsZero() {
		return time.Time{}, false
	}
	return min, true
}

func aggregatePaymentQuotes(quotes []*orchestratorv1.PaymentQuote) (*orchestratorv1.PaymentQuoteAggregate, error) {
	if len(quotes) == 0 {
		return nil, nil
	}
	debitTotals := map[string]decimal.Decimal{}
	settlementTotals := map[string]decimal.Decimal{}
	feeTotals := map[string]decimal.Decimal{}
	networkTotals := map[string]decimal.Decimal{}

	for _, quote := range quotes {
		if quote == nil {
			continue
		}
		if err := accumulateMoney(debitTotals, quote.GetDebitAmount()); err != nil {
			return nil, err
		}
		if err := accumulateMoney(settlementTotals, quote.GetExpectedSettlementAmount()); err != nil {
			return nil, err
		}
		if err := accumulateMoney(feeTotals, quote.GetExpectedFeeTotal()); err != nil {
			return nil, err
		}
		if nf := quote.GetNetworkFee(); nf != nil {
			if err := accumulateMoney(networkTotals, nf.GetNetworkFee()); err != nil {
				return nil, err
			}
		}
	}

	return &orchestratorv1.PaymentQuoteAggregate{
		DebitAmounts:              totalsToMoney(debitTotals),
		ExpectedSettlementAmounts: totalsToMoney(settlementTotals),
		ExpectedFeeTotals:         totalsToMoney(feeTotals),
		NetworkFeeTotals:          totalsToMoney(networkTotals),
	}, nil
}

func accumulateMoney(totals map[string]decimal.Decimal, money *moneyv1.Money) error {
	if money == nil {
		return nil
	}
	currency := strings.TrimSpace(money.GetCurrency())
	if currency == "" {
		return nil
	}
	amount, err := decimal.NewFromString(money.GetAmount())
	if err != nil {
		return err
	}
	if current, ok := totals[currency]; ok {
		totals[currency] = current.Add(amount)
		return nil
	}
	totals[currency] = amount
	return nil
}

func totalsToMoney(totals map[string]decimal.Decimal) []*moneyv1.Money {
	if len(totals) == 0 {
		return nil
	}
	currencies := make([]string, 0, len(totals))
	for currency := range totals {
		currencies = append(currencies, currency)
	}
	sort.Strings(currencies)

	result := make([]*moneyv1.Money, 0, len(currencies))
	for _, currency := range currencies {
		amount := totals[currency]
		result = append(result, &moneyv1.Money{
			Amount:   amount.String(),
			Currency: currency,
		})
	}
	return result
}

func intentsFromProto(intents []*orchestratorv1.PaymentIntent) []model.PaymentIntent {
	if len(intents) == 0 {
		return nil
	}
	result := make([]model.PaymentIntent, 0, len(intents))
	for _, intent := range intents {
		result = append(result, intentFromProto(intent))
	}
	return result
}

func quoteSnapshotsFromProto(quotes []*orchestratorv1.PaymentQuote) []*model.PaymentQuoteSnapshot {
	if len(quotes) == 0 {
		return nil
	}
	result := make([]*model.PaymentQuoteSnapshot, 0, len(quotes))
	for _, quote := range quotes {
		if quote == nil {
			continue
		}
		if snapshot := quoteSnapshotToModel(quote); snapshot != nil {
			result = append(result, snapshot)
		}
	}
	return result
}
