package simulation

import (
	"context"
	"slices"

	"git.cw.tr/mukan-network/mukan-sdk/testutil/simsx"
	sdk "git.cw.tr/mukan-network/mukan-sdk/types"
	"git.cw.tr/mukan-network/mukan-sdk/x/bank/types"
)

func MsgSendFactory() simsx.SimMsgFactoryFn[*types.MsgSend] {
	return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgSend) {
		from := testData.AnyAccount(reporter, simsx.WithSpendableBalance())
		to := testData.AnyAccount(reporter, simsx.ExcludeAccounts(from))
		coins := from.LiquidBalance().RandSubsetCoins(reporter, simsx.WithSendEnabledCoins())
		return []simsx.SimAccount{from}, types.NewMsgSend(from.Address, to.Address, coins)
	}
}

func MsgMultiSendFactory() simsx.SimMsgFactoryFn[*types.MsgMultiSend] {
	return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgMultiSend) {
		r := testData.Rand()
		var (
			sending              = make([]types.Input, 1)
			receiving            = make([]types.Output, r.Intn(3)+1)
			senderAcc            = make([]simsx.SimAccount, len(sending))
			totalSentCoins       sdk.Coins
			uniqueAccountsFilter = simsx.UniqueAccounts()
		)
		for i := range sending {
			// generate random input fields, ignore to address
			from := testData.AnyAccount(reporter, simsx.WithSpendableBalance(), uniqueAccountsFilter)
			if reporter.IsSkipped() {
				return nil, nil
			}
			coins := from.LiquidBalance().RandSubsetCoins(reporter, simsx.WithSendEnabledCoins())

			// set signer privkey
			senderAcc[i] = from

			// set next input and accumulate total sent coins
			sending[i] = types.NewInput(from.Address, coins)
			totalSentCoins = totalSentCoins.Add(coins...)
		}

		for i := range receiving {
			receiver := testData.AnyAccount(reporter)
			if reporter.IsSkipped() {
				return nil, nil
			}

			var outCoins sdk.Coins
			// split total sent coins into random subsets for output
			if i == len(receiving)-1 {
				// last one receives remaining amount
				outCoins = totalSentCoins
			} else {
				// take random subset of remaining coins for output
				// and update remaining coins
				outCoins = r.SubsetCoins(totalSentCoins)
				totalSentCoins = totalSentCoins.Sub(outCoins...)
			}

			receiving[i] = types.NewOutput(receiver.Address, outCoins)
		}

		// remove any entries that have no coins
		receiving = slices.DeleteFunc(receiving, func(o types.Output) bool {
			return o.Address == "" || o.Coins.Empty()
		})
		return senderAcc, &types.MsgMultiSend{Inputs: sending, Outputs: receiving}
	}
}

// MsgUpdateParamsFactory creates a gov proposal for param updates
func MsgUpdateParamsFactory() simsx.SimMsgFactoryFn[*types.MsgUpdateParams] {
	return func(_ context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgUpdateParams) {
		params := types.DefaultParams()
		params.DefaultSendEnabled = testData.Rand().Intn(2) == 0
		return nil, &types.MsgUpdateParams{
			Authority: testData.ModuleAccountAddress(reporter, "gov"),
			Params:    params,
		}
	}
}
