package simulation

// import (
// 	"fmt"
// 	"math/rand"

// 	"github.com/cosmos/cosmos-sdk/baseapp"
// 	"github.com/cosmos/cosmos-sdk/codec"
// 	"github.com/cosmos/cosmos-sdk/simapp/helpers"
// 	sdk "github.com/cosmos/cosmos-sdk/types"
// 	"github.com/cosmos/cosmos-sdk/x/simulation"

// 	appparams "github.com/kava-labs/kava/app/params"
// 	"github.com/kava-labs/kava/x/cdp/keeper"
// 	"github.com/kava-labs/kava/x/cdp/types"
// )

// // Simulation operation weights constants
// const (
// 	OpWeightMsgCdp = "op_weight_msg_cdp"
// )

// // WeightedOperations returns all the operations from the module with their respective weights
// func WeightedOperations(
// 	appParams simulation.AppParams, cdc *codec.Codec, ak types.AccountKeeper,
// 	k keeper.Keeper, pfk types.PricefeedKeeper,
// ) simulation.WeightedOperations {
// 	var weightMsgCdp int

// 	appParams.GetOrGenerate(cdc, OpWeightMsgCdp, &weightMsgCdp, nil,
// 		func(_ *rand.Rand) {
// 			weightMsgCdp = appparams.DefaultWeightMsgCdp
// 		},
// 	)

// 	return simulation.WeightedOperations{
// 		simulation.NewWeightedOperation(
// 			weightMsgCdp,
// 			SimulateMsgCdp(ak, k, pfk),
// 		),
// 	}
// }

// // SimulateMsgCdp generates a MsgCreateCdp or MsgDepositCdp with random values.
// func SimulateMsgCdp(ak types.AccountKeeper, k keeper.Keeper, pfk types.PricefeedKeeper) simulation.Operation {
// 	return func(
// 		r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
// 	) (simulation.OperationMsg, []simulation.FutureOperation, error) {

// 		simAccount, _ := simulation.RandomAcc(r, accs)
// 		acc := ak.GetAccount(ctx, simAccount.Address)
// 		if acc == nil {
// 			return simulation.NoOpMsg(types.ModuleName), nil, nil
// 		}

// 		coins := acc.GetCoins()
// 		collateralParams := k.GetParams(ctx).CollateralParams
// 		if len(collateralParams) == 0 {
// 			return simulation.NoOpMsg(types.ModuleName), nil, nil
// 		}

// 		randCollateralParam := collateralParams[r.Intn(len(collateralParams))]
// 		debtParam, _ := k.GetDebtParam(ctx, randCollateralParam.DebtLimit.Denom)
// 		if coins.AmountOf(randCollateralParam.Denom).IsZero() {
// 			return simulation.NoOpMsg(types.ModuleName), nil, nil
// 		}

// 		price, err := pfk.GetCurrentPrice(ctx, randCollateralParam.SpotMarketID)
// 		if err != nil {
// 			return simulation.NoOpMsg(types.ModuleName), nil, nil // pricefeed going down is an expected event
// 		}
// 		// convert the price to the same units as the debt param
// 		priceShifted := ShiftDec(price.Price, debtParam.ConversionFactor)

// 		spendableCoins := acc.SpendableCoins(ctx.BlockTime())
// 		fees, err := simulation.RandomFees(r, ctx, spendableCoins)
// 		if err != nil {
// 			return simulation.NoOpMsg(types.ModuleName), nil, err
// 		}
// 		spendableCoins = spendableCoins.Sub(fees)

// 		existingCDP, found := k.GetCdpByOwnerAndCollateralType(ctx, acc.GetAddress(), randCollateralParam.Type)
// 		if !found {
// 			// calculate the minimum amount of collateral that is needed to create a cdp with the debt floor amount of debt and the minimum liquidation ratio
// 			// (debtFloor * liquidationRatio)/priceShifted
// 			minCollateralDeposit := (sdk.NewDecFromInt(debtParam.DebtFloor).Mul(randCollateralParam.LiquidationRatio)).Quo(priceShifted)
// 			// convert to proper collateral units
// 			minCollateralDeposit = ShiftDec(minCollateralDeposit, randCollateralParam.ConversionFactor)
// 			// convert to integer and always round up
// 			minCollateralDepositRounded := minCollateralDeposit.TruncateInt().Add(sdk.OneInt())
// 			if spendableCoins.AmountOf(randCollateralParam.Denom).LT(minCollateralDepositRounded) {
// 				// account doesn't have enough funds to open a cdp for the min debt amount
// 				return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "insufficient funds to open cdp", false, nil), nil, nil
// 			}
// 			// set the max collateral deposit to the amount of coins in the account
// 			maxCollateralDeposit := spendableCoins.AmountOf(randCollateralParam.Denom)

// 			// randomly select a collateral deposit amount
// 			collateralDeposit := sdk.NewInt(int64(simulation.RandIntBetween(r, int(minCollateralDepositRounded.Int64()), int(maxCollateralDeposit.Int64()))))
// 			// calculate how much the randomly selected deposit is worth
// 			collateralDepositValue := ShiftDec(sdk.NewDecFromInt(collateralDeposit), randCollateralParam.ConversionFactor.Neg()).Mul(priceShifted)
// 			// calculate the max amount of debt that could be drawn for the chosen deposit
// 			maxDebtDraw := collateralDepositValue.Quo(randCollateralParam.LiquidationRatio).TruncateInt()
// 			// check that the debt limit hasn't been reached
// 			availableAssetDebt := randCollateralParam.DebtLimit.Amount.Sub(k.GetTotalPrincipal(ctx, randCollateralParam.Type, debtParam.Denom))
// 			if availableAssetDebt.LTE(debtParam.DebtFloor) {
// 				// debt limit has been reached
// 				return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "debt limit reached, cannot open cdp", false, nil), nil, nil
// 			}
// 			// ensure that the debt draw does not exceed the debt limit
// 			maxDebtDraw = sdk.MinInt(maxDebtDraw, availableAssetDebt)
// 			// randomly select a debt draw amount
// 			debtDraw := sdk.NewInt(int64(simulation.RandIntBetween(r, int(debtParam.DebtFloor.Int64()), int(maxDebtDraw.Int64()))))

// 			msg := types.NewMsgCreateCDP(acc.GetAddress(), sdk.NewCoin(randCollateralParam.Denom, collateralDeposit), sdk.NewCoin(debtParam.Denom, debtDraw), randCollateralParam.Type)

// 			tx := helpers.GenTx(
// 				[]sdk.Msg{msg},
// 				fees,
// 				helpers.DefaultGenTxGas,
// 				chainID,
// 				[]uint64{acc.GetAccountNumber()},
// 				[]uint64{acc.GetSequence()},
// 				simAccount.PrivKey,
// 			)

// 			_, _, err := app.Deliver(tx)
// 			if err != nil {
// 				// to aid debugging, add the stack trace to the comment field of the returned opMsg
// 				return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err
// 			}

// 			return simulation.NewOperationMsg(msg, true, ""), nil, nil
// 		}

// 		// a cdp already exists, deposit to it, draw debt from it, or repay debt to it
// 		// close 25% of the time
// 		if canClose(spendableCoins, existingCDP, debtParam.Denom) && shouldClose(r) {
// 			repaymentAmount := spendableCoins.AmountOf(debtParam.Denom)
// 			// check that repayment isn't below the debt floor
// 			if existingCDP.Principal.Amount.Add(existingCDP.AccumulatedFees.Amount).Sub(repaymentAmount).LT(debtParam.DebtFloor) {
// 				repaymentAmount = existingCDP.Principal.Amount.Add(existingCDP.AccumulatedFees.Amount).Sub(debtParam.DebtFloor)
// 			}
// 			msg := types.NewMsgRepayDebt(acc.GetAddress(), randCollateralParam.Type, sdk.NewCoin(debtParam.Denom, repaymentAmount))

// 			tx := helpers.GenTx(
// 				[]sdk.Msg{msg},
// 				fees,
// 				helpers.DefaultGenTxGas,
// 				chainID,
// 				[]uint64{acc.GetAccountNumber()},
// 				[]uint64{acc.GetSequence()},
// 				simAccount.PrivKey,
// 			)

// 			_, _, err := app.Deliver(tx)
// 			if err != nil {
// 				return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err
// 			}

// 			return simulation.NewOperationMsg(msg, true, ""), nil, nil
// 		}

// 		// deposit 25% of the time
// 		if hasCoins(spendableCoins, randCollateralParam.Denom) && shouldDeposit(r) {
// 			randDepositAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(spendableCoins.AmountOf(randCollateralParam.Denom).Int64()))))
// 			msg := types.NewMsgDeposit(acc.GetAddress(), acc.GetAddress(), sdk.NewCoin(randCollateralParam.Denom, randDepositAmount), randCollateralParam.Type)

// 			tx := helpers.GenTx(
// 				[]sdk.Msg{msg},
// 				fees,
// 				helpers.DefaultGenTxGas,
// 				chainID,
// 				[]uint64{acc.GetAccountNumber()},
// 				[]uint64{acc.GetSequence()},
// 				simAccount.PrivKey,
// 			)

// 			_, _, err := app.Deliver(tx)
// 			if err != nil {
// 				return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err
// 			}

// 			return simulation.NewOperationMsg(msg, true, ""), nil, nil
// 		}

// 		// draw debt 25% of the time
// 		if shouldDraw(r) {
// 			collateralShifted := ShiftDec(sdk.NewDecFromInt(existingCDP.Collateral.Amount), randCollateralParam.ConversionFactor.Neg())
// 			collateralValue := collateralShifted.Mul(priceShifted)
// 			newFeesAccumulated := k.CalculateNewInterest(ctx, existingCDP)
// 			totalFees := existingCDP.AccumulatedFees.Add(newFeesAccumulated)
// 			// given the current collateral value, calculate how much debt we could add while maintaining a valid liquidation ratio
// 			debt := existingCDP.Principal.Add(totalFees)
// 			maxTotalDebt := collateralValue.Quo(randCollateralParam.LiquidationRatio)
// 			maxDebt := (maxTotalDebt.Sub(sdk.NewDecFromInt(debt.Amount))).Mul(sdk.MustNewDecFromStr("0.95")).TruncateInt()
// 			if maxDebt.LTE(sdk.OneInt()) {
// 				// debt in cdp is maxed out
// 				return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "cdp debt maxed out, cannot draw more debt", false, nil), nil, nil
// 			}
// 			// check if the debt limit has been reached
// 			availableAssetDebt := randCollateralParam.DebtLimit.Amount.Sub(k.GetTotalPrincipal(ctx, randCollateralParam.Type, debtParam.Denom))
// 			if availableAssetDebt.LTE(sdk.OneInt()) {
// 				// debt limit has been reached
// 				return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "debt limit reached, cannot draw more debt", false, nil), nil, nil
// 			}
// 			maxDraw := sdk.MinInt(maxDebt, availableAssetDebt)

// 			randDrawAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(maxDraw.Int64()))))
// 			msg := types.NewMsgDrawDebt(acc.GetAddress(), randCollateralParam.Type, sdk.NewCoin(debtParam.Denom, randDrawAmount))

// 			tx := helpers.GenTx(
// 				[]sdk.Msg{msg},
// 				fees,
// 				helpers.DefaultGenTxGas,
// 				chainID,
// 				[]uint64{acc.GetAccountNumber()},
// 				[]uint64{acc.GetSequence()},
// 				simAccount.PrivKey,
// 			)

// 			_, _, err := app.Deliver(tx)
// 			if err != nil {
// 				return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err
// 			}

// 			return simulation.NewOperationMsg(msg, true, ""), nil, nil
// 		}

// 		// repay debt 25% of the time
// 		if hasCoins(spendableCoins, debtParam.Denom) {
// 			debt := existingCDP.Principal.Amount.Add(existingCDP.AccumulatedFees.Amount)
// 			payableDebt := debt.Sub(debtParam.DebtFloor)
// 			if payableDebt.IsZero() {
// 				return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "cannot make partial repayment, cdp at debt floor", false, nil), nil, nil
// 			}
// 			maxRepay := sdk.MinInt(
// 				spendableCoins.AmountOf(debtParam.Denom),
// 				payableDebt,
// 			)
// 			var randRepayAmount sdk.Int
// 			if maxRepay.Equal(sdk.OneInt()) {
// 				randRepayAmount = sdk.OneInt()
// 			} else {
// 				randRepayAmount = sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(maxRepay.Int64()))))
// 			}

// 			msg := types.NewMsgRepayDebt(acc.GetAddress(), randCollateralParam.Type, sdk.NewCoin(debtParam.Denom, randRepayAmount))

// 			tx := helpers.GenTx(
// 				[]sdk.Msg{msg},
// 				fees,
// 				helpers.DefaultGenTxGas,
// 				chainID,
// 				[]uint64{acc.GetAccountNumber()},
// 				[]uint64{acc.GetSequence()},
// 				simAccount.PrivKey,
// 			)

// 			_, _, err := app.Deliver(tx)
// 			if err != nil {
// 				return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err
// 			}

// 			return simulation.NewOperationMsg(msg, true, ""), nil, nil
// 		}

// 		return simulation.NoOpMsg(types.ModuleName), nil, nil
// 	}
// }

// func shouldDraw(r *rand.Rand) bool {
// 	threshold := 50
// 	value := simulation.RandIntBetween(r, 1, 100)
// 	return value > threshold
// }

// func shouldDeposit(r *rand.Rand) bool {
// 	threshold := 66
// 	value := simulation.RandIntBetween(r, 1, 100)
// 	return value > threshold
// }

// func hasCoins(spendableCoins sdk.Coins, denom string) bool {
// 	return spendableCoins.AmountOf(denom).IsPositive()
// }

// func shouldClose(r *rand.Rand) bool {
// 	threshold := 75
// 	value := simulation.RandIntBetween(r, 1, 100)
// 	return value > threshold
// }

// func canClose(spendableCoins sdk.Coins, c types.CDP, denom string) bool {
// 	repaymentAmount := c.Principal.Add(c.AccumulatedFees).Amount
// 	return spendableCoins.AmountOf(denom).GTE(repaymentAmount)
// }