package testutil

import (
	"context"

	"github.com/cosmos/cosmos-sdk/client"
	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
	sdk "github.com/cosmos/cosmos-sdk/types"
	"github.com/cosmos/cosmos-sdk/types/tx/signing"
	"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
	authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/evmos/ethermint/ethereum/eip712"
	emtypes "github.com/evmos/ethermint/types"
	evmtypes "github.com/evmos/ethermint/x/evm/types"
)

// NewEip712TxBuilder is a helper method for creating an EIP712 signed tx
// A tx like this is what a user signing cosmos messages with Metamask would broadcast.
func (suite *E2eTestSuite) NewEip712TxBuilder(
	acc *SigningAccount, chain *Chain, gas uint64, gasAmount sdk.Coins, msgs []sdk.Msg, memo string,
) client.TxBuilder {
	// get account details
	var accDetails authtypes.AccountI
	a, err := chain.Grpc.Query.Auth.Account(context.Background(), &authtypes.QueryAccountRequest{
		Address: acc.SdkAddress.String(),
	})
	suite.NoError(err)
	err = chain.EncodingConfig.InterfaceRegistry.UnpackAny(a.Account, &accDetails)
	suite.NoError(err)

	// get nonce & acc number
	nonce := accDetails.GetSequence()
	accNumber := accDetails.GetAccountNumber()

	// get chain id
	pc, err := emtypes.ParseChainID(chain.ChainID)
	suite.NoError(err)
	ethChainId := pc.Uint64()

	evmParams, err := chain.Grpc.Query.Evm.Params(context.Background(), &evmtypes.QueryParamsRequest{})
	suite.NoError(err)

	fee := legacytx.NewStdFee(gas, gasAmount)

	// build EIP712 tx
	// -- untyped data
	untypedData := eip712.ConstructUntypedEIP712Data(
		chain.ChainID,
		accNumber,
		nonce,
		0, // no timeout
		fee,
		msgs,
		memo,
		nil,
	)
	// -- typed data
	typedData, err := eip712.WrapTxToTypedData(ethChainId, msgs, untypedData, &eip712.FeeDelegationOptions{
		FeePayer: acc.SdkAddress,
	}, evmParams.Params)
	suite.NoError(err)

	// -- raw data hash!
	data, err := eip712.ComputeTypedDataHash(typedData)
	suite.NoError(err)

	// -- sign the hash
	signature, pubKey, err := acc.SignRawEvmData(data)
	suite.NoError(err)
	signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper

	// add ExtensionOptionsWeb3Tx extension
	var option *codectypes.Any
	option, err = codectypes.NewAnyWithValue(&emtypes.ExtensionOptionsWeb3Tx{
		FeePayer:         acc.SdkAddress.String(),
		TypedDataChainID: ethChainId,
		FeePayerSig:      signature,
	})
	suite.NoError(err)

	// create cosmos sdk tx builder
	txBuilder := chain.EncodingConfig.TxConfig.NewTxBuilder()
	builder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder)
	suite.True(ok)

	builder.SetExtensionOptions(option)
	builder.SetFeeAmount(fee.Amount)
	builder.SetGasLimit(fee.Gas)

	sigsV2 := signing.SignatureV2{
		PubKey: pubKey,
		Data: &signing.SingleSignatureData{
			SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
		},
		Sequence: nonce,
	}

	err = builder.SetSignatures(sigsV2)
	suite.Require().NoError(err)

	err = builder.SetMsgs(msgs...)
	suite.Require().NoError(err)

	builder.SetMemo(memo)

	return builder
}
