package service

import (
	"bytes"
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	tronaddr "github.com/fbsobreira/gotron-sdk/pkg/address"
	troncore "github.com/fbsobreira/gotron-sdk/pkg/proto/core"
	"google.golang.org/protobuf/reflect/protoreflect"
	"math/big"
)

const (
	ethUsdt  = "0xdAC17F958D2ee523a2206206994597C13D831ec7"
	ethUsdc  = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
	tronUsdt = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
	tronUsdc = "TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8"
)

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

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

func checkErc(tx *types.Transaction, whitelists map[string]struct{}) (string, string, string, error) {
	if tx.To().String() == ethUsdt || tx.To().String() == ethUsdc {
		input := tx.Data()
		var methodId [4]byte
		copy(methodId[:], input[:4])

		if !(bytes.Equal(methodId[:], transferId) || bytes.Equal(methodId[:], approveId)) {
			return "", "", "", errors.New("erc20: only sign transfer and approve")
		}
		unpack, err := arguments.Unpack(input[4:])
		if err != nil {
			return "", "", "", err
		}

		to, ok := unpack[0].(common.Address)
		if !ok {
			return "", "", "", errors.New("erc20: to parameter error")
		}

		amount, ok := unpack[1].(*big.Int)
		if !ok {
			return "", "", "", errors.New("erc20: amount parameter error")
		}

		if _, exist := whitelists[to.String()]; !exist {
			return "", "", "", errors.New("erc20: to is not a whitelist address")
		}

		coin := usdtCoin
		if tx.To().String() == ethUsdc {
			coin = usdcCoin
		}

		return coin, to.String(), amount.String(), nil
	} else {
		if _, exist := whitelists[tx.To().String()]; !exist {
			return "", "", "", errors.New("eth: to is not a whitelist address")
		}
	}

	return ethCoin, tx.To().String(), tx.Value().String(), nil
}

func checkTron(tx *troncore.Transaction, whitelists map[string]struct{}) (string, string, string, error) {
	if len(tx.GetRawData().Contract) != 1 {
		return "", "", "", errors.New("Transaction_Contract length must be 1")
	}
	contract := tx.GetRawData().Contract[0]

	var c interface{}
	switch contract.Type {
	case troncore.Transaction_Contract_TransferContract:
		c = &troncore.TransferContract{}
	case troncore.Transaction_Contract_TriggerSmartContract:
		c = &troncore.TriggerSmartContract{}
	default:
		return "", "", "", errors.New("Transaction_Contract Type must be Transaction_Contract_TransferContract and Transaction_Contract_TriggerSmartContract")
	}

	if err := contract.GetParameter().UnmarshalTo(c.(protoreflect.ProtoMessage)); err != nil {
		return "", "", "", fmt.Errorf("proto unmarshal any: %+w", err)
	}

	if contract.Type == troncore.Transaction_Contract_TransferContract {
		transferContract := c.(*troncore.TransferContract)
		to := tronaddr.Address(transferContract.ToAddress)
		if _, exist := whitelists[to.String()]; !exist {
			return "", "", "", errors.New("trx: to is not a whitelist address")
		}

		return trxCoin, to.String(), big.NewInt(0).SetInt64(transferContract.Amount).String(), nil
	} else {
		triggerSmartContract := c.(*troncore.TriggerSmartContract)
		contractAddr := tronaddr.Address(triggerSmartContract.ContractAddress)
		if contractAddr.String() != tronUsdt && contractAddr.String() != tronUsdc {
			return "", "", "", errors.New("trc20: only sign usdt and usdc")
		}

		input := triggerSmartContract.Data
		var methodId [4]byte
		copy(methodId[:], input[:4])

		if !(bytes.Equal(methodId[:], transferId) || bytes.Equal(methodId[:], approveId)) {
			return "", "", "", errors.New("trc20: only sign transfer and approve")
		}

		unpack, err := arguments.Unpack(input[4:])
		if err != nil {
			return "", "", "", err
		}

		to, ok := unpack[0].(common.Address)
		if !ok {
			return "", "", "", errors.New("trc20: to parameter error")
		}

		amount, ok := unpack[1].(*big.Int)
		if !ok {
			return "", "", "", errors.New("erc20: amount parameter error")
		}

		toTron := make([]byte, 0)
		toTron = append(toTron, tronaddr.TronBytePrefix)
		toTron = append(toTron, to.Bytes()...)
		if _, exist := whitelists[tronaddr.Address(toTron).String()]; !exist {
			return "", "", "", errors.New("trc20: to is not a whitelist address")
		}

		coin := usdtCoin
		if contractAddr.String() == tronUsdc {
			coin = usdcCoin
		}

		return coin, to.String(), amount.String(), nil
	}
}
