package service

import (
	"context"
	"encoding/hex"
	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/ethclient"
	"math/big"
	"time"
)

var (
	ethUsdt = common.HexToAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7")
	ethUsdc = common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
)

var (
	transferId, _ = hex.DecodeString("a9059cbb")
)

var (
	addressTy, _ = abi.NewType("address", "", nil)
	uint256Ty, _ = abi.NewType("uint256", "", nil)
	arguments    = abi.Arguments{{Type: addressTy}, {Type: uint256Ty}}
)

func buildTransferEth(ethClient *ethclient.Client, from, to string, amount *big.Int) (*types.Transaction, error) {
	fromAddr := common.HexToAddress(from)
	toAddr := common.HexToAddress(to)

	return createTransaction(ethClient, &fromAddr, &toAddr, amount, nil)
}

func buildTransferUsdtOfEth(ethClient *ethclient.Client, from, to string, amount *big.Int) (*types.Transaction, error) {
	fromAddr := common.HexToAddress(from)
	toAddr := common.HexToAddress(to)

	data, err := arguments.Pack(toAddr, amount)
	if err != nil {
		return nil, err
	}
	return createTransaction(ethClient, &fromAddr, &ethUsdt, big.NewInt(0), append(transferId, data...))
}

func buildTransferUsdcOfEth(ethClient *ethclient.Client, from, to string, amount *big.Int) (*types.Transaction, error) {
	fromAddr := common.HexToAddress(from)
	toAddr := common.HexToAddress(to)

	data, err := arguments.Pack(toAddr, amount)
	if err != nil {
		return nil, err
	}
	return createTransaction(ethClient, &fromAddr, &ethUsdc, big.NewInt(0), append(transferId, data...))
}

func createTransaction(ethClient *ethclient.Client, from, to *common.Address, amount *big.Int, data []byte) (*types.Transaction, error) {
	tx := &types.DynamicFeeTx{
		ChainID: big.NewInt(1),
		To:      to,
		Value:   amount,
		Data:    data,
	}

	err := estimateGas(ethClient, from, tx)
	if err != nil {
		return nil, err
	}

	return types.NewTx(tx), nil
}

func estimateGas(ethClient *ethclient.Client, from *common.Address, tx *types.DynamicFeeTx) error {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
	defer cancel()

	head, err := ethClient.HeaderByNumber(ctx, nil)
	if err != nil {
		return err
	}

	gasTipCap, err := ethClient.SuggestGasTipCap(ctx)
	if err != nil {
		return err
	}
	gasFeeCap := new(big.Int).Add(
		gasTipCap,
		new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
	)

	msg := ethereum.CallMsg{
		From:      *from,
		To:        tx.To,
		GasPrice:  nil,
		GasTipCap: gasTipCap,
		GasFeeCap: gasFeeCap,
		Value:     tx.Value,
		Data:      tx.Data,
	}

	gasLimit, err := ethClient.EstimateGas(ctx, msg)
	if err != nil {
		return err
	}

	nonce, err := ethClient.PendingNonceAt(ctx, *from)
	if err != nil {
		return err
	}

	tx.Nonce = nonce
	tx.Gas = gasLimit
	tx.GasFeeCap = gasFeeCap
	tx.GasTipCap = gasTipCap

	return nil
}
