package simulation

import (
	"math/rand"

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

	sdk "github.com/cosmos/cosmos-sdk/types"
	authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
	vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
	vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
	"github.com/cosmos/cosmos-sdk/x/simulation"

	"github.com/kava-labs/kava/x/validator-vesting/types"
)

// RandomizedGenState generates a random GenesisState for validator-vesting
func RandomizedGenState(simState *module.SimulationState) {
	var authGenState authtypes.GenesisState
	authSimState := simState.GenState[authtypes.ModuleName]
	simState.Cdc.MustUnmarshalJSON(authSimState, &authGenState)
	var newGenesisAccs authexported.GenesisAccounts
	for _, acc := range authGenState.Accounts {
		va, ok := acc.(vestexported.VestingAccount)
		if ok {
			// 50% of the time convert the vesting account

			if simState.Rand.Intn(100) < 50 {
				bacc := authtypes.NewBaseAccountWithAddress(va.GetAddress())
				err := bacc.SetCoins(va.GetCoins())
				if err != nil {
					panic(err)
				}

				duration := va.GetEndTime() - va.GetStartTime()
				vestingPeriods := getRandomVestingPeriods(duration, simState.Rand, va.GetCoins())
				vestingCoins := getVestingCoins(vestingPeriods)
				bva, _ := vestingtypes.NewBaseVestingAccount(&bacc, vestingCoins, va.GetEndTime())
				var gacc authexported.GenesisAccount
				if simState.Rand.Intn(100) < 50 {
					// convert to periodic vesting account 50%
					gacc = vestingtypes.NewPeriodicVestingAccountRaw(bva, va.GetStartTime(), vestingPeriods)
					err = gacc.Validate()
					if err != nil {
						panic(err)
					}
				} else {
					var val int
					if simState.NumBonded <= 1 {
						val = 0
					} else {
						val = simulation.RandIntBetween(simState.Rand, 0, int(simState.NumBonded)-1)
					}
					consAdd := getRandomValidatorConsAddr(simState, val)
					// convert to validator vesting account 50%
					// set signing threshold to be anywhere between 1 and 100
					gacc = types.NewValidatorVestingAccountRaw(
						bva, va.GetStartTime(), vestingPeriods, consAdd, nil,
						int64(simulation.RandIntBetween(simState.Rand, 1, 100)),
					)
					err = gacc.Validate()
					if err != nil {
						panic(err)
					}
				}
				newGenesisAccs = append(newGenesisAccs, gacc)
			} else {
				newGenesisAccs = append(newGenesisAccs, acc)
			}
		} else {
			newGenesisAccs = append(newGenesisAccs, acc)
		}
	}
	newAuthGenesis := authtypes.NewGenesisState(authGenState.Params, newGenesisAccs)
	simState.GenState[authtypes.ModuleName] = simState.Cdc.MustMarshalJSON(newAuthGenesis)
	vestGenState := types.DefaultGenesisState()
	simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(vestGenState)
}

func getRandomValidatorConsAddr(simState *module.SimulationState, rint int) sdk.ConsAddress {
	acc := simState.Accounts[rint]
	return sdk.ConsAddress(acc.PubKey.Address())
}

func getRandomVestingPeriods(duration int64, r *rand.Rand, origCoins sdk.Coins) vestingtypes.Periods {
	maxPeriods := int64(50)
	if duration <= 0 {
		panic("duration in getRandomVestingPeriods cannot be <= 0")
	}
	if duration < maxPeriods {
		maxPeriods = duration
	}
	numPeriods := r.Intn(int(maxPeriods)) + 1 // ensures >= 1, 0 check above prevents Intn panicking
	lenPeriod := duration / int64(numPeriods)
	periodLengths := make([]int64, numPeriods)
	totalLength := int64(0)
	for i := 0; i < numPeriods; i++ {
		periodLengths[i] = lenPeriod
		totalLength += lenPeriod
	}
	if duration-totalLength != 0 {
		periodLengths[len(periodLengths)-1] += (duration - totalLength)
	}

	coinFraction := simulation.RandIntBetween(r, 1, 100)
	vestingCoins := sdk.NewCoins()
	for _, ic := range origCoins {
		amountVesting := ic.Amount.Int64() / int64(coinFraction)
		vestingCoins = vestingCoins.Add(sdk.NewInt64Coin(ic.Denom, amountVesting))
	}
	periodCoins := sdk.NewCoins()
	for _, c := range vestingCoins {
		amountPeriod := c.Amount.Int64() / int64(numPeriods)
		periodCoins = periodCoins.Add(sdk.NewInt64Coin(c.Denom, amountPeriod))
	}

	vestingPeriods := make([]vestingtypes.Period, numPeriods)
	for i := 0; i < numPeriods; i++ {
		vestingPeriods[i] = vestingtypes.Period{Length: int64(periodLengths[i]), Amount: periodCoins}
	}

	return vestingPeriods

}

func getVestingCoins(periods vestingtypes.Periods) sdk.Coins {
	vestingCoins := sdk.NewCoins()
	for _, p := range periods {
		vestingCoins = vestingCoins.Add(p.Amount...)
	}
	return vestingCoins
}
