package keeper_test

import (
	sdk "git.cw.tr/mukan-network/mukan-sdk/types"
	authtypes "git.cw.tr/mukan-network/mukan-sdk/x/auth/types"
	banktypes "git.cw.tr/mukan-network/mukan-sdk/x/bank/types"
	govtypes "git.cw.tr/mukan-network/mukan-sdk/x/gov/types"
)

var govAcc = authtypes.NewEmptyModuleAccount(govtypes.ModuleName, authtypes.Minter)

func (suite *KeeperTestSuite) TestMsgUpdateParams() {
	// default params
	params := banktypes.DefaultParams()

	testCases := []struct {
		name      string
		input     *banktypes.MsgUpdateParams
		expErr    bool
		expErrMsg string
	}{
		{
			name: "invalid authority",
			input: &banktypes.MsgUpdateParams{
				Authority: "invalid",
				Params:    params,
			},
			expErr:    true,
			expErrMsg: "invalid authority",
		},
		{
			name: "send enabled param",
			input: &banktypes.MsgUpdateParams{
				Authority: suite.bankKeeper.GetAuthority(),
				Params: banktypes.Params{
					SendEnabled: []*banktypes.SendEnabled{
						{Denom: "foo", Enabled: true},
					},
				},
			},
			expErr:    true,
			expErrMsg: "use of send_enabled in params is no longer supported",
		},
		{
			name: "all good",
			input: &banktypes.MsgUpdateParams{
				Authority: suite.bankKeeper.GetAuthority(),
				Params:    params,
			},
			expErr: false,
		},
	}

	for _, tc := range testCases {
		suite.Run(tc.name, func() {
			_, err := suite.msgServer.UpdateParams(suite.ctx, tc.input)

			if tc.expErr {
				suite.Require().Error(err)
				suite.Require().Contains(err.Error(), tc.expErrMsg)
			} else {
				suite.Require().NoError(err)
			}
		})
	}
}

func (suite *KeeperTestSuite) TestMsgSend() {
	origCoins := sdk.NewCoins(sdk.NewInt64Coin("sendableCoin", 100))
	suite.bankKeeper.SetSendEnabled(suite.ctx, origCoins.Denoms()[0], true)
	atom0 := sdk.NewCoins(sdk.NewInt64Coin("atom", 0))
	atom123eth0 := sdk.Coins{sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 0)}

	testCases := []struct {
		name      string
		input     *banktypes.MsgSend
		expErr    bool
		expErrMsg string
	}{
		{
			name: "invalid send to blocked address",
			input: &banktypes.MsgSend{
				FromAddress: minterAcc.GetAddress().String(),
				ToAddress:   accAddrs[4].String(),
				Amount:      origCoins,
			},
			expErr:    true,
			expErrMsg: "is not allowed to receive funds",
		},
		{
			name: "invalid coins",
			input: &banktypes.MsgSend{
				FromAddress: minterAcc.GetAddress().String(),
				ToAddress:   baseAcc.Address,
				Amount:      atom0,
			},
			expErr:    true,
			expErrMsg: "invalid coins",
		},
		{
			name: "123atom,0eth: invalid coins",
			input: &banktypes.MsgSend{
				FromAddress: minterAcc.GetAddress().String(),
				ToAddress:   baseAcc.Address,
				Amount:      atom123eth0,
			},
			expErr:    true,
			expErrMsg: "123atom,0eth: invalid coins",
		},
		{
			name: "invalid from address: empty address string is not allowed: invalid address",
			input: &banktypes.MsgSend{
				FromAddress: "",
				ToAddress:   baseAcc.Address,
				Amount:      origCoins,
			},
			expErr:    true,
			expErrMsg: "empty address string is not allowed",
		},
		{
			name: "invalid to address: empty address string is not allowed: invalid address",
			input: &banktypes.MsgSend{
				FromAddress: minterAcc.GetAddress().String(),
				ToAddress:   "",
				Amount:      origCoins,
			},
			expErr:    true,
			expErrMsg: "empty address string is not allowed",
		},
		{
			name: "all good",
			input: &banktypes.MsgSend{
				FromAddress: minterAcc.GetAddress().String(),
				ToAddress:   baseAcc.Address,
				Amount:      origCoins,
			},
			expErr: false,
		},
	}

	for _, tc := range testCases {
		suite.Run(tc.name, func() {
			suite.mockMintCoins(minterAcc)
			_ = suite.bankKeeper.MintCoins(suite.ctx, minterAcc.Name, origCoins)
			if !tc.expErr {
				suite.mockSendCoins(suite.ctx, minterAcc, baseAcc.GetAddress())
			}
			_, err := suite.msgServer.Send(suite.ctx, tc.input)
			if tc.expErr {
				suite.Require().Error(err)
				suite.Require().Contains(err.Error(), tc.expErrMsg)
			} else {
				suite.Require().NoError(err)
			}
		})
	}
}

func (suite *KeeperTestSuite) TestMsgMultiSend() {
	origDenom := "sendableCoin"
	origCoins := sdk.NewCoins(sdk.NewInt64Coin(origDenom, 100))
	sendCoins := sdk.NewCoins(sdk.NewInt64Coin(origDenom, 50))
	suite.bankKeeper.SetSendEnabled(suite.ctx, origDenom, true)

	testCases := []struct {
		name      string
		input     *banktypes.MsgMultiSend
		expErr    bool
		expErrMsg string
	}{
		{
			name:      "no inputs to send transaction",
			input:     &banktypes.MsgMultiSend{},
			expErr:    true,
			expErrMsg: "no inputs to send transaction",
		},
		{
			name: "no inputs to send transaction",
			input: &banktypes.MsgMultiSend{
				Outputs: []banktypes.Output{
					{Address: accAddrs[4].String(), Coins: sendCoins},
				},
			},
			expErr:    true,
			expErrMsg: "no inputs to send transaction",
		},
		{
			name: "more than one inputs to send transaction",
			input: &banktypes.MsgMultiSend{
				Inputs: []banktypes.Input{
					{Address: minterAcc.GetAddress().String(), Coins: origCoins},
					{Address: minterAcc.GetAddress().String(), Coins: origCoins},
				},
			},
			expErr:    true,
			expErrMsg: "multiple senders not allowed",
		},
		{
			name: "no outputs to send transaction",
			input: &banktypes.MsgMultiSend{
				Inputs: []banktypes.Input{
					{Address: minterAcc.GetAddress().String(), Coins: origCoins},
				},
			},
			expErr:    true,
			expErrMsg: "no outputs to send transaction",
		},
		{
			name: "invalid send to blocked address",
			input: &banktypes.MsgMultiSend{
				Inputs: []banktypes.Input{
					{Address: minterAcc.GetAddress().String(), Coins: origCoins},
				},
				Outputs: []banktypes.Output{
					{Address: accAddrs[0].String(), Coins: sendCoins},
					{Address: accAddrs[4].String(), Coins: sendCoins},
				},
			},
			expErr:    true,
			expErrMsg: "is not allowed to receive funds",
		},
		{
			name: "invalid send to blocked address",
			input: &banktypes.MsgMultiSend{
				Inputs: []banktypes.Input{
					{Address: minterAcc.GetAddress().String(), Coins: origCoins},
				},
				Outputs: []banktypes.Output{
					{Address: accAddrs[0].String(), Coins: sendCoins},
					{Address: accAddrs[1].String(), Coins: sendCoins},
				},
			},
			expErr: false,
		},
	}

	for _, tc := range testCases {
		suite.Run(tc.name, func() {
			suite.mockMintCoins(minterAcc)
			_ = suite.bankKeeper.MintCoins(suite.ctx, minterAcc.Name, origCoins)
			if !tc.expErr {
				suite.mockInputOutputCoins([]sdk.AccountI{minterAcc}, accAddrs[:2])
			}
			_, err := suite.msgServer.MultiSend(suite.ctx, tc.input)
			if tc.expErr {
				suite.Require().Error(err)
				suite.Require().Contains(err.Error(), tc.expErrMsg)
			} else {
				suite.Require().NoError(err)
			}
		})
	}
}

func (suite *KeeperTestSuite) TestMsgSetSendEnabled() {
	testCases := []struct {
		name     string
		req      *banktypes.MsgSetSendEnabled
		isExpErr bool
		errMsg   string
	}{
		{
			name: "all good",
			req: banktypes.NewMsgSetSendEnabled(
				govAcc.GetAddress().String(),
				[]*banktypes.SendEnabled{
					banktypes.NewSendEnabled("atom1", true),
				},
				[]string{},
			),
		},
		{
			name: "all good with two denoms",
			req: banktypes.NewMsgSetSendEnabled(
				govAcc.GetAddress().String(),
				[]*banktypes.SendEnabled{
					banktypes.NewSendEnabled("atom1", true),
					banktypes.NewSendEnabled("atom2", true),
				},
				[]string{"defcoinc", "defcoind"},
			),
		},
		{
			name: "duplicate denoms",
			req: banktypes.NewMsgSetSendEnabled(
				govAcc.GetAddress().String(),
				[]*banktypes.SendEnabled{
					banktypes.NewSendEnabled("atom", true),
					banktypes.NewSendEnabled("atom", true),
				},
				[]string{},
			),
			isExpErr: true,
			errMsg:   `duplicate denom entries found for "atom": invalid request`,
		},
		{
			name: "bad first denom name, (invalid send enabled denom present in list)",
			req: banktypes.NewMsgSetSendEnabled(
				govAcc.GetAddress().String(),
				[]*banktypes.SendEnabled{
					banktypes.NewSendEnabled("not a denom", true),
					banktypes.NewSendEnabled("somecoin", true),
				},
				[]string{},
			),
			isExpErr: true,
			errMsg:   `invalid SendEnabled denom "not a denom": invalid denom: not a denom: invalid request`,
		},
		{
			name: "bad second denom name, (invalid send enabled denom present in list)",
			req: banktypes.NewMsgSetSendEnabled(
				govAcc.GetAddress().String(),
				[]*banktypes.SendEnabled{
					banktypes.NewSendEnabled("somecoin", true),
					banktypes.NewSendEnabled("not a denom", true),
				},
				[]string{},
			),
			isExpErr: true,
			errMsg:   `invalid SendEnabled denom "not a denom": invalid denom: not a denom: invalid request`,
		},
		{
			name: "invalid UseDefaultFor denom",
			req: banktypes.NewMsgSetSendEnabled(
				govAcc.GetAddress().String(),
				[]*banktypes.SendEnabled{
					banktypes.NewSendEnabled("atom", true),
				},
				[]string{"not a denom"},
			),
			isExpErr: true,
			errMsg:   `invalid UseDefaultFor denom "not a denom": invalid denom: not a denom: invalid request`,
		},
		{
			name: "invalid authority",
			req: banktypes.NewMsgSetSendEnabled(
				"invalid",
				[]*banktypes.SendEnabled{
					banktypes.NewSendEnabled("atom", true),
				},
				[]string{},
			),
			isExpErr: true,
		},
	}

	for _, tc := range testCases {
		suite.Run(tc.name, func() {
			_, err := suite.msgServer.SetSendEnabled(suite.ctx, tc.req)

			if tc.isExpErr {
				suite.Require().Error(err)
				suite.Require().Contains(err.Error(), tc.errMsg)
			} else {
				suite.Require().NoError(err)
			}
		})
	}
}
