package testutil

import (
	"time"

	sdk "github.com/cosmos/cosmos-sdk/types"
	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"

	distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
	"github.com/kava-labs/kava/app"
	types "github.com/kava-labs/kava/x/community/types"
	kavadisttypes "github.com/kava-labs/kava/x/kavadist/types"
)

func (suite *disableInflationTestSuite) TestStartCommunityFundConsolidation() {
	tests := []struct {
		name                   string
		initialFeePoolCoins    sdk.DecCoins
		initialKavadistBalance sdk.Coins
	}{
		{
			"basic test with both balances and dust",
			sdk.NewDecCoins(
				sdk.NewDecCoinFromDec("ukava", sdk.NewDecWithPrec(123456, 2)),
				sdk.NewDecCoinFromDec("usdx", sdk.NewDecWithPrec(654321, 3)),
			),
			sdk.NewCoins(
				sdk.NewInt64Coin("ukava", 10_000),
				sdk.NewInt64Coin("usdx", 10_000),
			),
		},
		{
			"empty x/distribution feepool",
			sdk.DecCoins(nil),
			sdk.NewCoins(
				sdk.NewInt64Coin("ukava", 10_000),
				sdk.NewInt64Coin("usdx", 10_000),
			),
		},
		{
			"empty x/kavadist balance",
			sdk.NewDecCoins(
				sdk.NewDecCoinFromDec("ukava", sdk.NewDecWithPrec(123456, 2)),
				sdk.NewDecCoinFromDec("usdx", sdk.NewDecWithPrec(654321, 3)),
			),
			sdk.Coins{},
		},
		{
			"both x/distribution feepool and x/kavadist balance empty",
			sdk.DecCoins(nil),
			sdk.Coins{},
		},
	}

	for _, tc := range tests {
		suite.Run(tc.name, func() {
			suite.SetupTest()
			ak := suite.App.GetAccountKeeper()

			initialFeePool := distrtypes.FeePool{
				CommunityPool: tc.initialFeePoolCoins,
			}

			initialFeePoolCoins, initialFeePoolDust := initialFeePool.CommunityPool.TruncateDecimal()

			// More coins than initial feepool/communitypool
			fundCoins := sdk.NewCoins(
				sdk.NewInt64Coin("ukava", 10_000),
				sdk.NewInt64Coin("usdx", 10_000),
			)

			// Always fund x/distribution with enough coins to cover feepool
			err := suite.App.FundModuleAccount(
				suite.Ctx,
				distrtypes.ModuleName,
				fundCoins,
			)
			suite.NoError(err, "x/distribution account should be funded without error")

			err = suite.App.FundModuleAccount(
				suite.Ctx,
				kavadisttypes.ModuleName,
				tc.initialKavadistBalance,
			)
			suite.NoError(err, "x/kavadist account should be funded without error")

			suite.App.GetDistrKeeper().SetFeePool(suite.Ctx, initialFeePool)

			// Ensure the feepool was set before migration
			feePoolBefore := suite.App.GetDistrKeeper().GetFeePool(suite.Ctx)
			suite.Equal(initialFeePool, feePoolBefore, "initial feepool should be set")
			communityBalanceBefore := suite.App.GetCommunityKeeper().GetModuleAccountBalance(suite.Ctx)

			kavadistAcc := ak.GetModuleAccount(suite.Ctx, kavadisttypes.KavaDistMacc)
			kavaDistCoinsBefore := suite.App.GetBankKeeper().GetAllBalances(suite.Ctx, kavadistAcc.GetAddress())
			suite.Equal(
				tc.initialKavadistBalance,
				kavaDistCoinsBefore,
				"x/kavadist balance should be funded",
			)

			expectedKavaDistCoins := sdk.NewCoins(sdk.NewCoin("ukava", kavaDistCoinsBefore.AmountOf("ukava")))

			// -------------
			// Run upgrade

			params, found := suite.Keeper.GetParams(suite.Ctx)
			suite.Require().True(found)
			params.UpgradeTimeDisableInflation = suite.Ctx.BlockTime().Add(-time.Minute)
			suite.Keeper.SetParams(suite.Ctx, params)

			err = suite.Keeper.StartCommunityFundConsolidation(suite.Ctx)
			suite.NoError(err, "consolidation should not error")

			// -------------
			// Check results
			suite.Run("module balances after consolidation should moved", func() {
				feePoolAfter := suite.App.GetDistrKeeper().GetFeePool(suite.Ctx)
				suite.Equal(
					initialFeePoolDust,
					feePoolAfter.CommunityPool,
					"x/distribution community pool should be sent to x/community",
				)

				kavaDistCoinsAfter := suite.App.GetBankKeeper().GetAllBalances(suite.Ctx, kavadistAcc.GetAddress())
				suite.Equal(
					expectedKavaDistCoins,
					kavaDistCoinsAfter,
					"x/kavadist balance should ony contain ukava",
				)

				totalExpectedCommunityPoolCoins := communityBalanceBefore.
					Add(initialFeePoolCoins...).      // x/distribution fee pool
					Add(tc.initialKavadistBalance...) // x/kavadist module balance

				communityBalanceAfter := suite.App.GetCommunityKeeper().GetModuleAccountBalance(suite.Ctx)

				// Use .IsAllGTE to avoid types.Coins(nil) vs types.Coins{} mismatch
				suite.Truef(
					totalExpectedCommunityPoolCoins.IsAllGTE(communityBalanceAfter),
					"x/community balance should be increased by the truncated x/distribution community pool, got %s, expected %s",
					communityBalanceAfter,
					totalExpectedCommunityPoolCoins,
				)
			})

			suite.Run("bank transfer events should be emitted", func() {
				communityAcc := ak.GetModuleAccount(suite.Ctx, types.ModuleAccountName)
				distributionAcc := ak.GetModuleAccount(suite.Ctx, distrtypes.ModuleName)
				kavadistAcc := ak.GetModuleAccount(suite.Ctx, kavadisttypes.KavaDistMacc)

				events := suite.Ctx.EventManager().Events()

				suite.NoError(
					app.EventsContains(
						events,
						sdk.NewEvent(
							banktypes.EventTypeTransfer,
							sdk.NewAttribute(banktypes.AttributeKeyRecipient, communityAcc.GetAddress().String()),
							sdk.NewAttribute(banktypes.AttributeKeySender, distributionAcc.GetAddress().String()),
							sdk.NewAttribute(sdk.AttributeKeyAmount, initialFeePoolCoins.String()),
						),
					),
				)

				suite.NoError(
					app.EventsContains(
						events,
						sdk.NewEvent(
							banktypes.EventTypeTransfer,
							sdk.NewAttribute(banktypes.AttributeKeyRecipient, communityAcc.GetAddress().String()),
							sdk.NewAttribute(banktypes.AttributeKeySender, kavadistAcc.GetAddress().String()),
							sdk.NewAttribute(sdk.AttributeKeyAmount, kavaDistCoinsBefore.Sub(expectedKavaDistCoins...).String()),
						),
					),
				)
			})
		})
	}
}