package keeper_test

import (
	sdkmath "cosmossdk.io/math"
	sdk "github.com/cosmos/cosmos-sdk/types"

	"github.com/kava-labs/kava/x/evmutil/testutil"
	"github.com/kava-labs/kava/x/evmutil/types"
)

func (suite *MsgServerSuite) TestConvertCoinToERC20_Bep3() {
	invoker, err := sdk.AccAddressFromBech32("kava123fxg0l602etulhhcdm0vt7l57qya5wjcrwhzz")
	receiverAddr := testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
	suite.Require().NoError(err)

	type errArgs struct {
		expectPass bool
		contains   string
	}

	tests := []struct {
		name                  string
		msg                   types.MsgConvertCoinToERC20
		userBankBalance       sdkmath.Int
		expUserErc20Balance   sdkmath.Int
		moduleErc20Balance    sdkmath.Int
		expModuleErc20Balance sdkmath.Int
		errArgs               errArgs
	}{
		{
			name: "valid",
			msg: types.NewMsgConvertCoinToERC20(
				invoker.String(),
				receiverAddr.String(),
				sdk.NewCoin("bnb", sdkmath.NewInt(1234)),
			),
			userBankBalance:       sdkmath.NewInt(1_234),
			moduleErc20Balance:    sdkmath.NewInt(10e13),
			expUserErc20Balance:   sdkmath.NewInt(1.234e13),
			expModuleErc20Balance: sdkmath.NewInt(8.766e13),
			errArgs: errArgs{
				expectPass: true,
			},
		},
		{
			name: "invalid - invalid receiver address",
			msg: types.NewMsgConvertCoinToERC20(
				invoker.String(),
				"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc",
				sdk.NewCoin("bnb", sdkmath.NewInt(1234)),
			),
			userBankBalance:    sdkmath.NewInt(1_234),
			moduleErc20Balance: sdkmath.NewInt(1e14),
			errArgs: errArgs{
				expectPass: false,
				contains:   "invalid Receiver address: string is not a hex address",
			},
		},
		{
			name: "invalid - initiator receiver address",
			msg: types.NewMsgConvertCoinToERC20(
				"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc",
				receiverAddr.String(),
				sdk.NewCoin("bnb", sdkmath.NewInt(1234)),
			),
			userBankBalance:    sdkmath.NewInt(1_234),
			moduleErc20Balance: sdkmath.NewInt(1e14),
			errArgs: errArgs{
				expectPass: false,
				contains:   "invalid Initiator address",
			},
		},
	}

	for _, tc := range tests {
		suite.Run(tc.name, func() {
			suite.SetupTest()
			contractAddr := suite.DeployERC20()

			// enable conversion pair
			params := suite.Keeper.GetParams(suite.Ctx)
			params.EnabledConversionPairs[0].Denom = tc.msg.Amount.Denom
			suite.Keeper.SetParams(suite.Ctx, params)

			pair := types.NewConversionPair(
				contractAddr,
				tc.msg.Amount.Denom,
			)

			// fund user & module account
			if tc.userBankBalance.GT(sdkmath.ZeroInt()) {
				err = suite.App.FundAccount(
					suite.Ctx,
					invoker,
					sdk.NewCoins(sdk.NewCoin(pair.Denom, tc.userBankBalance)),
				)
				suite.Require().NoError(err)
			}
			if tc.moduleErc20Balance.GT(sdkmath.ZeroInt()) {
				err := suite.Keeper.MintERC20(
					suite.Ctx,
					pair.GetAddress(), // contractAddr
					types.NewInternalEVMAddress(types.ModuleEVMAddress), //receiver
					tc.moduleErc20Balance.BigInt(),
				)
				suite.Require().NoError(err)
			}

			_, err = suite.msgServer.ConvertCoinToERC20(sdk.WrapSDKContext(suite.Ctx), &tc.msg)

			if tc.errArgs.expectPass {
				suite.Require().NoError(err)

				// validate user balance
				bal := suite.GetERC20BalanceOf(
					types.ERC20MintableBurnableContract.ABI,
					pair.GetAddress(),
					receiverAddr,
				)
				suite.Require().Equal(
					tc.expUserErc20Balance.BigInt().Int64(),
					bal.Int64(),
					"user erc20 balance should match expected amount",
				)

				// validate module balance
				bal = suite.GetERC20BalanceOf(
					types.ERC20MintableBurnableContract.ABI,
					pair.GetAddress(),
					types.NewInternalEVMAddress(types.ModuleEVMAddress),
				)
				suite.Require().Equal(
					tc.expModuleErc20Balance.BigInt().Int64(),
					bal.Int64(),
					"module erc20 balance should match expected amount",
				)

				// msg server event
				suite.EventsContains(suite.GetEvents(),
					sdk.NewEvent(
						sdk.EventTypeMessage,
						sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
						sdk.NewAttribute(sdk.AttributeKeySender, tc.msg.Initiator),
					))

				// keeper event
				suite.EventsContains(suite.GetEvents(),
					sdk.NewEvent(
						types.EventTypeConvertCoinToERC20,
						sdk.NewAttribute(types.AttributeKeyInitiator, tc.msg.Initiator),
						sdk.NewAttribute(types.AttributeKeyReceiver, tc.msg.Receiver),
						sdk.NewAttribute(types.AttributeKeyERC20Address, pair.GetAddress().String()),
						sdk.NewAttribute(types.AttributeKeyAmount, tc.msg.Amount.String()),
					))

			} else {
				suite.Require().Error(err)
				suite.Require().Contains(err.Error(), tc.errArgs.contains)
			}
		})
	}
}

func (suite *MsgServerSuite) TestConvertERC20ToCoin_Bep3() {
	invoker := testutil.MustNewInternalEVMAddressFromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
	invokerCosmosAddr, err := sdk.AccAddressFromHexUnsafe(invoker.String()[2:])
	suite.Require().NoError(err)
	contractAddr := suite.DeployERC20()

	type errArgs struct {
		expectPass bool
		contains   string
	}

	tests := []struct {
		name                string
		msg                 types.MsgConvertERC20ToCoin
		userErc20Balance    sdkmath.Int
		expUserBankBalance  sdkmath.Int
		expUserErc20Balance sdkmath.Int
		errArgs             errArgs
	}{
		{
			name: "valid",
			msg: types.NewMsgConvertERC20ToCoin(
				invoker,
				invokerCosmosAddr,
				contractAddr,
				sdkmath.NewInt(1.0031e18),
			),
			userErc20Balance:    sdkmath.NewInt(1.12e18),
			expUserBankBalance:  sdkmath.NewInt(1.0031e8),
			expUserErc20Balance: sdkmath.NewInt(0.1169e18),
			errArgs: errArgs{
				expectPass: true,
			},
		},
		{
			name: "invalid - invalid initiator address",
			msg: types.MsgConvertERC20ToCoin{
				Initiator:        "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc",
				Receiver:         invokerCosmosAddr.String(),
				KavaERC20Address: contractAddr.String(),
				Amount:           sdkmath.NewInt(12e8),
			},
			userErc20Balance: sdkmath.NewInt(2e18),
			errArgs: errArgs{
				expectPass: false,
				contains:   "invalid initiator address: string is not a hex address",
			},
		},
		{
			name: "invalid - invalid receiver address",
			msg: types.MsgConvertERC20ToCoin{
				Initiator:        invoker.String(),
				Receiver:         "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc",
				KavaERC20Address: contractAddr.String(),
				Amount:           sdkmath.NewInt(12e8),
			},
			userErc20Balance: sdkmath.NewInt(2e18),
			errArgs: errArgs{
				expectPass: false,
				contains:   "invalid receiver address",
			},
		},
		{
			name: "invalid - invalid contract address",
			msg: types.MsgConvertERC20ToCoin{
				Initiator:        invoker.String(),
				Receiver:         invokerCosmosAddr.String(),
				KavaERC20Address: invokerCosmosAddr.String(),
				Amount:           sdkmath.NewInt(12e8),
			},
			userErc20Balance: sdkmath.NewInt(2e18),
			errArgs: errArgs{
				expectPass: false,
				contains:   "invalid contract address",
			},
		},
	}

	for _, tc := range tests {
		suite.Run(tc.name, func() {
			suite.SetupTest()
			contractAddr := suite.DeployERC20()

			conversionDenom := "bnb"
			params := suite.Keeper.GetParams(suite.Ctx)
			params.EnabledConversionPairs[0].Denom = conversionDenom
			suite.Keeper.SetParams(suite.Ctx, params)

			pair := types.NewConversionPair(
				contractAddr,
				conversionDenom,
			)

			// fund user erc20 balance
			if tc.userErc20Balance.GT(sdkmath.ZeroInt()) {
				err := suite.Keeper.MintERC20(
					suite.Ctx,
					pair.GetAddress(),
					invoker,
					tc.userErc20Balance.BigInt(),
				)
				suite.Require().NoError(err)
			}

			// create user account, otherwise `CallEVMWithData` will fail due to failing to get user account when finding its sequence.
			err = suite.App.FundAccount(suite.Ctx, invokerCosmosAddr, sdk.NewCoins(sdk.NewCoin(conversionDenom, sdk.ZeroInt())))
			suite.Require().NoError(err)

			_, err := suite.msgServer.ConvertERC20ToCoin(sdk.WrapSDKContext(suite.Ctx), &tc.msg)

			if tc.errArgs.expectPass {
				suite.Require().NoError(err)

				// validate user balance after conversion
				bal := suite.GetERC20BalanceOf(
					types.ERC20MintableBurnableContract.ABI,
					pair.GetAddress(),
					testutil.MustNewInternalEVMAddressFromString(invoker.String()),
				)
				suite.Require().Equal(tc.expUserErc20Balance.BigInt().Int64(), bal.Int64(), "user erc20 balance is invalid")

				// validate user coin balance
				coinBal := suite.App.GetBankKeeper().GetBalance(suite.Ctx, invokerCosmosAddr, pair.Denom)
				suite.Require().Equal(tc.expUserBankBalance, coinBal.Amount, "user coin balance is invalid")

				// msg server event
				suite.EventsContains(suite.GetEvents(),
					sdk.NewEvent(
						sdk.EventTypeMessage,
						sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
						sdk.NewAttribute(sdk.AttributeKeySender, tc.msg.Initiator),
					))

				// keeper event
				suite.EventsContains(suite.GetEvents(),
					sdk.NewEvent(
						types.EventTypeConvertERC20ToCoin,
						sdk.NewAttribute(types.AttributeKeyERC20Address, pair.GetAddress().String()),
						sdk.NewAttribute(types.AttributeKeyInitiator, tc.msg.Initiator),
						sdk.NewAttribute(types.AttributeKeyReceiver, tc.msg.Receiver),
						sdk.NewAttribute(types.AttributeKeyAmount, sdk.NewCoin(pair.Denom, tc.expUserBankBalance).String()),
					))
			} else {
				suite.Require().Error(err)
				suite.Require().Contains(err.Error(), tc.errArgs.contains)
			}
		})
	}
}