package keeper_test

import (
	"fmt"
	"testing"

	sdk "github.com/cosmos/cosmos-sdk/types"
	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
	"github.com/kava-labs/kava/x/precisebank/types"
	"github.com/stretchr/testify/require"
)

// Testing module name for mocked GetModuleAccount()
const burnerModuleName = "burner-module"

func TestBurnCoins_PanicValidations(t *testing.T) {
	// panic tests for invalid inputs

	tests := []struct {
		name            string
		recipientModule string
		setupFn         func(td testData)
		burnAmount      sdk.Coins
		wantPanic       string
	}{
		{
			"invalid module",
			"notamodule",
			func(td testData) {
				// Make module not found
				td.ak.EXPECT().
					GetModuleAccount(td.ctx, "notamodule").
					Return(nil).
					Once()
			},
			cs(c("ukava", 1000)),
			"module account notamodule does not exist: unknown address",
		},
		{
			"no permission",
			burnerModuleName,
			func(td testData) {
				td.ak.EXPECT().
					GetModuleAccount(td.ctx, burnerModuleName).
					Return(authtypes.NewModuleAccount(
						authtypes.NewBaseAccountWithAddress(sdk.AccAddress{1}),
						burnerModuleName,
						// no burn permission
					)).
					Once()
			},
			cs(c("ukava", 1000)),
			fmt.Sprintf("module account %s does not have permissions to burn tokens: unauthorized", burnerModuleName),
		},
		{
			"has burn permission",
			burnerModuleName,
			func(td testData) {
				td.ak.EXPECT().
					GetModuleAccount(td.ctx, burnerModuleName).
					Return(authtypes.NewModuleAccount(
						authtypes.NewBaseAccountWithAddress(sdk.AccAddress{1}),
						burnerModuleName,
						// includes burner permission
						authtypes.Burner,
					)).
					Once()

				// Will call x/bank BurnCoins coins
				td.bk.EXPECT().
					BurnCoins(td.ctx, burnerModuleName, cs(c("ukava", 1000))).
					Return(nil).
					Once()
			},
			cs(c("ukava", 1000)),
			"",
		},
		{
			"disallow burning from x/precisebank",
			types.ModuleName,
			func(td testData) {
				// No mock setup needed since this is checked before module
				// account checks
			},
			cs(c("ukava", 1000)),
			"module account precisebank cannot be burned from: unauthorized",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			td := NewMockedTestData(t)
			tt.setupFn(td)

			if tt.wantPanic != "" {
				require.PanicsWithError(t, tt.wantPanic, func() {
					_ = td.keeper.BurnCoins(td.ctx, tt.recipientModule, tt.burnAmount)
				})
				return
			}

			require.NotPanics(t, func() {
				// Not testing errors, only panics for this test
				_ = td.keeper.BurnCoins(td.ctx, tt.recipientModule, tt.burnAmount)
			})
		})
	}
}

func TestBurnCoins_Errors(t *testing.T) {
	// returned errors, not panics

	tests := []struct {
		name            string
		recipientModule string
		setupFn         func(td testData)
		burnAmount      sdk.Coins
		wantError       string
	}{
		{
			"invalid coins",
			burnerModuleName,
			func(td testData) {
				// Valid module account burner
				td.ak.EXPECT().
					GetModuleAccount(td.ctx, burnerModuleName).
					Return(authtypes.NewModuleAccount(
						authtypes.NewBaseAccountWithAddress(sdk.AccAddress{1}),
						burnerModuleName,
						// includes burner permission
						authtypes.Burner,
					)).
					Once()
			},
			sdk.Coins{sdk.Coin{
				Denom:  "ukava",
				Amount: sdk.NewInt(-1000),
			}},
			"-1000ukava: invalid coins",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			td := NewMockedTestData(t)
			tt.setupFn(td)

			require.NotPanics(t, func() {
				err := td.keeper.BurnCoins(td.ctx, tt.recipientModule, tt.burnAmount)

				if tt.wantError != "" {
					require.Error(t, err)
					require.EqualError(t, err, tt.wantError)
					return
				}

				require.NoError(t, err)
			})
		})
	}
}