package quotation

import (
	"testing"

	fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
	oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
	quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
)

func TestComputeAggregates_FeeTreatmentMatrix(t *testing.T) {
	fxQuote := &oraclev1.Quote{
		Pair: &fxv1.CurrencyPair{
			Base:  "USDT",
			Quote: "RUB",
		},
		Price: &moneyv1.Decimal{
			Value: "76",
		},
		Side: fxv1.Side_SELL_BASE_BUY_QUOTE,
	}

	cases := []struct {
		name           string
		pay            *moneyv1.Money
		settlement     *moneyv1.Money
		fee            *moneyv1.Money
		networkFee     *moneyv1.Money
		fxQuote        *oraclev1.Quote
		feeTreatment   quotationv2.FeeTreatment
		wantDebit      string
		wantSettlement string
	}{
		{
			name:           "unspecified fee treatment defaults to add_to_source",
			pay:            &moneyv1.Money{Amount: "100", Currency: "USD"},
			settlement:     &moneyv1.Money{Amount: "100", Currency: "USD"},
			fee:            &moneyv1.Money{Amount: "10", Currency: "USD"},
			networkFee:     &moneyv1.Money{Amount: "2", Currency: "USD"},
			feeTreatment:   quotationv2.FeeTreatment_FEE_TREATMENT_UNSPECIFIED,
			wantDebit:      "112",
			wantSettlement: "100",
		},
		{
			name:           "add_to_source charges payer for fee and network",
			pay:            &moneyv1.Money{Amount: "100", Currency: "USD"},
			settlement:     &moneyv1.Money{Amount: "100", Currency: "USD"},
			fee:            &moneyv1.Money{Amount: "10", Currency: "USD"},
			networkFee:     &moneyv1.Money{Amount: "2", Currency: "USD"},
			feeTreatment:   quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE,
			wantDebit:      "112",
			wantSettlement: "100",
		},
		{
			name:           "deduct_from_destination charges recipient for fee and network",
			pay:            &moneyv1.Money{Amount: "100", Currency: "USD"},
			settlement:     &moneyv1.Money{Amount: "100", Currency: "USD"},
			fee:            &moneyv1.Money{Amount: "10", Currency: "USD"},
			networkFee:     &moneyv1.Money{Amount: "2", Currency: "USD"},
			feeTreatment:   quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION,
			wantDebit:      "100",
			wantSettlement: "88",
		},
		{
			name:           "add_to_source keeps settlement amount unchanged in FX path",
			pay:            &moneyv1.Money{Amount: "100", Currency: "USDT"},
			settlement:     &moneyv1.Money{Amount: "7600", Currency: "RUB"},
			fee:            &moneyv1.Money{Amount: "1", Currency: "USDT"},
			networkFee:     &moneyv1.Money{Amount: "1", Currency: "USDT"},
			fxQuote:        fxQuote,
			feeTreatment:   quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE,
			wantDebit:      "102",
			wantSettlement: "7600",
		},
		{
			name:           "deduct_from_destination converts charges in FX path",
			pay:            &moneyv1.Money{Amount: "100", Currency: "USDT"},
			settlement:     &moneyv1.Money{Amount: "7600", Currency: "RUB"},
			fee:            &moneyv1.Money{Amount: "1", Currency: "USDT"},
			networkFee:     &moneyv1.Money{Amount: "1", Currency: "USDT"},
			fxQuote:        fxQuote,
			feeTreatment:   quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION,
			wantDebit:      "100",
			wantSettlement: "7448",
		},
		{
			name:           "no charges keeps aggregates intact",
			pay:            &moneyv1.Money{Amount: "100", Currency: "USD"},
			settlement:     &moneyv1.Money{Amount: "100", Currency: "USD"},
			feeTreatment:   quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION,
			wantDebit:      "100",
			wantSettlement: "100",
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			var network *chainv1.EstimateTransferFeeResponse
			if tc.networkFee != nil {
				network = &chainv1.EstimateTransferFeeResponse{NetworkFee: tc.networkFee}
			}

			debit, settlement := computeAggregates(
				tc.pay,
				tc.settlement,
				tc.fee,
				network,
				tc.fxQuote,
				tc.feeTreatment,
			)
			if debit == nil || settlement == nil {
				t.Fatalf("expected aggregate amounts")
			}
			if got, want := debit.GetAmount(), tc.wantDebit; got != want {
				t.Fatalf("unexpected debit amount: got=%s want=%s", got, want)
			}
			if got, want := settlement.GetAmount(), tc.wantSettlement; got != want {
				t.Fatalf("unexpected settlement amount: got=%s want=%s", got, want)
			}
		})
	}
}

func TestComputeAggregates_NilPayReturnsNil(t *testing.T) {
	debit, settlement := computeAggregates(
		nil,
		&moneyv1.Money{Amount: "100", Currency: "USD"},
		&moneyv1.Money{Amount: "10", Currency: "USD"},
		nil,
		nil,
		quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE,
	)
	if debit != nil || settlement != nil {
		t.Fatalf("expected nil aggregates, got debit=%v settlement=%v", debit, settlement)
	}
}
