package orchestrator

import (
	"testing"
	"time"

	"github.com/shopspring/decimal"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
)

func TestAggregatePaymentQuotes(t *testing.T) {
	quotes := []*orchestratorv1.PaymentQuote{
		{
			DebitAmount:              &moneyv1.Money{Amount: "10", Currency: "USD"},
			ExpectedSettlementAmount: &moneyv1.Money{Amount: "8", Currency: "EUR"},
			ExpectedFeeTotal:         &moneyv1.Money{Amount: "1", Currency: "USD"},
			NetworkFee: &chainv1.EstimateTransferFeeResponse{
				NetworkFee: &moneyv1.Money{Amount: "0.5", Currency: "USD"},
			},
		},
		{
			DebitAmount:              &moneyv1.Money{Amount: "5", Currency: "USD"},
			ExpectedSettlementAmount: &moneyv1.Money{Amount: "1000", Currency: "NGN"},
			ExpectedFeeTotal:         &moneyv1.Money{Amount: "2", Currency: "USD"},
			NetworkFee: &chainv1.EstimateTransferFeeResponse{
				NetworkFee: &moneyv1.Money{Amount: "100", Currency: "NGN"},
			},
		},
	}

	agg, err := aggregatePaymentQuotes(quotes)
	if err != nil {
		t.Fatalf("aggregatePaymentQuotes returned error: %v", err)
	}

	assertMoneyTotals(t, agg.GetDebitAmounts(), map[string]string{"USD": "15"})
	assertMoneyTotals(t, agg.GetExpectedSettlementAmounts(), map[string]string{"EUR": "8", "NGN": "1000"})
	assertMoneyTotals(t, agg.GetExpectedFeeTotals(), map[string]string{"USD": "3"})
	assertMoneyTotals(t, agg.GetNetworkFeeTotals(), map[string]string{"USD": "0.5", "NGN": "100"})
}

func TestAggregatePaymentQuotesInvalidAmount(t *testing.T) {
	quotes := []*orchestratorv1.PaymentQuote{
		{
			DebitAmount: &moneyv1.Money{Amount: "bad", Currency: "USD"},
		},
	}
	if _, err := aggregatePaymentQuotes(quotes); err == nil {
		t.Fatal("expected error for invalid amount")
	}
}

func TestMinQuoteExpiry(t *testing.T) {
	now := time.Now().UTC()
	later := now.Add(10 * time.Minute)
	earliest := now.Add(5 * time.Minute)

	min, ok := minQuoteExpiry([]time.Time{later, {}, earliest})
	if !ok {
		t.Fatal("expected min expiry to be set")
	}
	if !min.Equal(earliest) {
		t.Fatalf("expected min expiry %v, got %v", earliest, min)
	}

	if _, ok := minQuoteExpiry([]time.Time{{}}); ok {
		t.Fatal("expected min expiry to be unset")
	}
}

func assertMoneyTotals(t *testing.T, list []*moneyv1.Money, expected map[string]string) {
	t.Helper()
	got := make(map[string]decimal.Decimal, len(list))
	for _, item := range list {
		if item == nil {
			continue
		}
		val, err := decimal.NewFromString(item.GetAmount())
		if err != nil {
			t.Fatalf("invalid money amount %q: %v", item.GetAmount(), err)
		}
		got[item.GetCurrency()] = val
	}
	if len(got) != len(expected) {
		t.Fatalf("expected %d totals, got %d", len(expected), len(got))
	}
	for currency, amount := range expected {
		val, err := decimal.NewFromString(amount)
		if err != nil {
			t.Fatalf("invalid expected amount %q: %v", amount, err)
		}
		gotVal, ok := got[currency]
		if !ok {
			t.Fatalf("missing currency %s", currency)
		}
		if !gotVal.Equal(val) {
			t.Fatalf("expected %s %s, got %s", amount, currency, gotVal.String())
		}
	}
}
