package keeper_test

import (
	"testing"

	"github.com/stretchr/testify/suite"

	sdk "github.com/cosmos/cosmos-sdk/types"

	"github.com/kava-labs/kava/x/incentive/types"
	savingstypes "github.com/kava-labs/kava/x/savings/types"
)

// InitializeSavingsRewardTests runs unit tests for the keeper.InitializeSavingsReward method
type InitializeSavingsRewardTests struct {
	unitTester
}

func TestInitializeSavingsRewardTests(t *testing.T) {
	suite.Run(t, new(InitializeSavingsRewardTests))
}

func (suite *InitializeSavingsRewardTests) TestClaimAddedWhenClaimDoesNotExistAndNoRewards() {
	// When a claim doesn't exist, and a user deposits to a non-rewarded pool;
	// then a claim is added with no rewards and no indexes

	// no global indexes stored as this pool is not rewarded

	owner := arbitraryAddress()

	amount := sdk.NewCoin("test", sdk.OneInt())
	deposit := savingstypes.NewDeposit(owner, sdk.NewCoins(amount))

	suite.keeper.InitializeSavingsReward(suite.ctx, deposit)

	syncedClaim, found := suite.keeper.GetSavingsClaim(suite.ctx, owner)
	suite.True(found)
	// A new claim should have empty indexes. It doesn't strictly need the poolID either.
	expectedIndexes := types.MultiRewardIndexes{{
		CollateralType: amount.Denom,
		RewardIndexes:  nil,
	}}
	suite.Equal(expectedIndexes, syncedClaim.RewardIndexes)
	// a new claim should start with 0 rewards
	suite.Equal(sdk.Coins(nil), syncedClaim.Reward)
}

func (suite *InitializeSavingsRewardTests) TestClaimAddedWhenClaimDoesNotExistAndRewardsExist() {
	// When a claim doesn't exist, and a user deposits to a rewarded pool;
	// then a claim is added with no rewards and indexes matching the global indexes

	amount := sdk.NewCoin("test", sdk.OneInt())

	globalIndexes := types.MultiRewardIndexes{
		{
			CollateralType: amount.Denom,
			RewardIndexes: types.RewardIndexes{
				{
					CollateralType: "rewarddenom",
					RewardFactor:   d("1000.001"),
				},
			},
		},
	}
	suite.storeGlobalSavingsIndexes(globalIndexes)

	owner := arbitraryAddress()

	deposit := savingstypes.NewDeposit(owner, sdk.NewCoins(amount))
	suite.keeper.InitializeSavingsReward(suite.ctx, deposit)

	syncedClaim, found := suite.keeper.GetSavingsClaim(suite.ctx, owner)
	suite.True(found)
	// a new claim should start with the current global indexes
	suite.Equal(globalIndexes, syncedClaim.RewardIndexes)
	// a new claim should start with 0 rewards
	suite.Equal(sdk.Coins(nil), syncedClaim.Reward)
}

func (suite *InitializeSavingsRewardTests) TestClaimUpdatedWhenClaimExistsAndNoRewards() {
	// When a claim exists, and a user deposits to a new non-rewarded denom;
	// then the claim's rewards don't change

	preexistingDenom := "preexisting"
	preexistingIndexes := types.RewardIndexes{
		{
			CollateralType: "rewarddenom",
			RewardFactor:   d("1000.001"),
		},
	}

	claim := types.SavingsClaim{
		BaseMultiClaim: types.BaseMultiClaim{
			Owner:  arbitraryAddress(),
			Reward: arbitraryCoins(),
		},
		RewardIndexes: types.MultiRewardIndexes{
			{
				CollateralType: preexistingDenom,
				RewardIndexes:  preexistingIndexes,
			},
		},
	}
	suite.storeSavingsClaim(claim)

	// no global indexes stored as the new denom is not rewarded
	newDenom := "test"
	deposit := savingstypes.NewDeposit(claim.Owner, sdk.NewCoins(sdk.NewCoin(newDenom, sdk.OneInt())))
	suite.keeper.InitializeSavingsReward(suite.ctx, deposit)

	syncedClaim, found := suite.keeper.GetSavingsClaim(suite.ctx, claim.Owner)
	suite.True(found)

	// The preexisting indexes shouldn't be changed. It doesn't strictly need the new denom either.
	expectedIndexes := types.MultiRewardIndexes{
		{
			CollateralType: preexistingDenom,
			RewardIndexes:  preexistingIndexes,
		},
		{
			CollateralType: newDenom,
			RewardIndexes:  nil,
		},
	}
	suite.Equal(expectedIndexes, syncedClaim.RewardIndexes)
	// init should never alter the rewards
	suite.Equal(claim.Reward, syncedClaim.Reward)
}

func (suite *InitializeSavingsRewardTests) TestClaimUpdatedWhenClaimExistsAndRewardsExist() {
	// When a claim exists, and a user deposits to a new rewarded denom;
	// then the claim's rewards don't change and the indexes are updated to match the global indexes

	preexistingDenom := "preexisting"
	preexistingIndexes := types.RewardIndexes{
		{
			CollateralType: "rewarddenom",
			RewardFactor:   d("1000.001"),
		},
	}

	newDenom := "test"
	newIndexes := types.RewardIndexes{
		{
			CollateralType: "otherrewarddenom",
			RewardFactor:   d("1000.001"),
		},
	}

	claim := types.SavingsClaim{
		BaseMultiClaim: types.BaseMultiClaim{
			Owner:  arbitraryAddress(),
			Reward: arbitraryCoins(),
		},
		RewardIndexes: types.MultiRewardIndexes{
			{
				CollateralType: preexistingDenom,
				RewardIndexes:  preexistingIndexes,
			},
		},
	}
	suite.storeSavingsClaim(claim)

	globalIndexes := types.MultiRewardIndexes{
		{
			CollateralType: preexistingDenom,
			RewardIndexes:  increaseRewardFactors(preexistingIndexes),
		},
		{
			CollateralType: newDenom,
			RewardIndexes:  newIndexes,
		},
	}
	suite.storeGlobalSavingsIndexes(globalIndexes)

	deposit := savingstypes.NewDeposit(claim.Owner, sdk.NewCoins(sdk.NewCoin(newDenom, sdk.OneInt())))
	suite.keeper.InitializeSavingsReward(suite.ctx, deposit)

	syncedClaim, _ := suite.keeper.GetSavingsClaim(suite.ctx, claim.Owner)
	// only the indexes for the new denom should be updated
	expectedIndexes := types.MultiRewardIndexes{
		{
			CollateralType: preexistingDenom,
			RewardIndexes:  preexistingIndexes,
		},
		{
			CollateralType: newDenom,
			RewardIndexes:  newIndexes,
		},
	}
	suite.Equal(expectedIndexes, syncedClaim.RewardIndexes)
	// init should never alter the rewards
	suite.Equal(claim.Reward, syncedClaim.Reward)
}