package tron

import (
	"testing"

	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/require"
	"github.com/tech/sendico/gateway/tron/shared"
	"github.com/tech/sendico/gateway/tron/storage/model"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
)

func TestComputeGasTopUp_BalanceSufficient(t *testing.T) {
	network := tronNetwork(defaultPolicy())
	wallet := &model.ManagedWallet{}

	topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("5"), tronMoney("30"))
	require.NoError(t, err)
	require.Nil(t, topUp)
	require.True(t, decision.TopUpTRX.IsZero())
}

func TestComputeGasTopUp_BufferedRequired(t *testing.T) {
	network := tronNetwork(defaultPolicy())
	wallet := &model.ManagedWallet{}

	topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("50"), tronMoney("10"))
	require.NoError(t, err)
	require.NotNil(t, topUp)
	require.Equal(t, "46000000", topUp.GetAmount())
	require.Equal(t, "TRX", topUp.GetCurrency())
	require.Equal(t, "46", decision.TopUpTRX.String())
}

func TestComputeGasTopUp_MinBalanceBinding(t *testing.T) {
	network := tronNetwork(defaultPolicy())
	wallet := &model.ManagedWallet{}

	topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("5"), tronMoney("1"))
	require.NoError(t, err)
	require.NotNil(t, topUp)
	require.Equal(t, "19000000", topUp.GetAmount())
	require.Equal(t, "19", decision.TopUpTRX.String())
}

func TestComputeGasTopUp_RoundsUp(t *testing.T) {
	policy := shared.GasTopUpPolicy{
		Default: shared.GasTopUpRule{
			BufferPercent:    decimal.NewFromFloat(0),
			MinNativeBalance: decimal.NewFromFloat(0),
			RoundingUnit:     decimal.NewFromFloat(1),
			MaxTopUp:         decimal.NewFromFloat(100),
		},
	}
	network := tronNetwork(&policy)
	wallet := &model.ManagedWallet{}

	topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("1.1"), tronMoney("0"))
	require.NoError(t, err)
	require.NotNil(t, topUp)
	require.Equal(t, "2000000", topUp.GetAmount())
	require.Equal(t, "2", decision.TopUpTRX.String())
}

func TestComputeGasTopUp_CapHit(t *testing.T) {
	policy := shared.GasTopUpPolicy{
		Default: shared.GasTopUpRule{
			BufferPercent:    decimal.NewFromFloat(0),
			MinNativeBalance: decimal.NewFromFloat(0),
			RoundingUnit:     decimal.NewFromFloat(1),
			MaxTopUp:         decimal.NewFromFloat(10),
		},
	}
	network := tronNetwork(&policy)
	wallet := &model.ManagedWallet{}

	topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("100"), tronMoney("0"))
	require.NoError(t, err)
	require.NotNil(t, topUp)
	require.Equal(t, "10000000", topUp.GetAmount())
	require.True(t, decision.CapHit)
}

func TestComputeGasTopUp_MinBalanceWhenRequiredZero(t *testing.T) {
	network := tronNetwork(defaultPolicy())
	wallet := &model.ManagedWallet{}

	topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("0"), tronMoney("5"))
	require.NoError(t, err)
	require.NotNil(t, topUp)
	require.Equal(t, "15000000", topUp.GetAmount())
	require.Equal(t, "15", decision.TopUpTRX.String())
}

func TestComputeGasTopUp_ContractPolicyOverride(t *testing.T) {
	policy := shared.GasTopUpPolicy{
		Default: shared.GasTopUpRule{
			BufferPercent:    decimal.NewFromFloat(0.1),
			MinNativeBalance: decimal.NewFromFloat(10),
			RoundingUnit:     decimal.NewFromFloat(1),
			MaxTopUp:         decimal.NewFromFloat(100),
		},
		Contract: &shared.GasTopUpRule{
			BufferPercent:    decimal.NewFromFloat(0.5),
			MinNativeBalance: decimal.NewFromFloat(5),
			RoundingUnit:     decimal.NewFromFloat(1),
			MaxTopUp:         decimal.NewFromFloat(100),
		},
	}
	network := tronNetwork(&policy)
	wallet := &model.ManagedWallet{ContractAddress: "0xcontract"}

	topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("10"), tronMoney("0"))
	require.NoError(t, err)
	require.NotNil(t, topUp)
	require.Equal(t, "15000000", topUp.GetAmount())
	require.Equal(t, "15", decision.TopUpTRX.String())
	require.Equal(t, "trc20", decision.OperationType)
}

func defaultPolicy() *shared.GasTopUpPolicy {
	return &shared.GasTopUpPolicy{
		Default: shared.GasTopUpRule{
			BufferPercent:    decimal.NewFromFloat(0.15),
			MinNativeBalance: decimal.NewFromFloat(20),
			RoundingUnit:     decimal.NewFromFloat(1),
			MaxTopUp:         decimal.NewFromFloat(500),
		},
	}
}

func tronNetwork(policy *shared.GasTopUpPolicy) shared.Network {
	return shared.Network{
		Name:           "tron_mainnet",
		NativeToken:    "TRX",
		GasTopUpPolicy: policy,
	}
}

func tronMoney(trx string) *moneyv1.Money {
	value, _ := decimal.NewFromString(trx)
	baseUnits := value.Mul(tronBaseUnitFactor).Truncate(0)
	return &moneyv1.Money{
		Currency: "TRX",
		Amount:   baseUnits.StringFixed(0),
	}
}
