package tron2

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math/big"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/JFJun/trx-sign-go/grpcs"
	"github.com/JFJun/trx-sign-go/sign"
	"github.com/btcsuite/btcd/btcec"
	addr "github.com/fbsobreira/gotron-sdk/pkg/address"
	"github.com/fbsobreira/gotron-sdk/pkg/common"
	"github.com/fbsobreira/gotron-sdk/pkg/proto/core"
	"github.com/google/uuid"
	"github.com/sirupsen/logrus"
	"github.com/spf13/viper"
)

type GenAddressInfo struct {
	Seed   string
	Addr   string
	PrvKey string
}

func GenAddressBySeed() (*GenAddressInfo, error) {

	uuid := uuid.New()
	seed := uuid.String()
	seed = strings.Replace(seed, "-", "", -1)

	if len(seed) != 32 {
		return nil, fmt.Errorf("seed len=[%d] is not equal 32", len(seed))
	}

	priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), []byte(seed))
	if priv == nil {
		return nil, errors.New("priv is nil ptr")
	}

	a := addr.PubkeyToAddress(priv.ToECDSA().PublicKey)

	addrInfo := GenAddressInfo{
		Seed:   seed,
		Addr:   a.String(),
		PrvKey: priv.D.Text(16),
	}

	return &addrInfo, nil
}

type txInfoItem struct {
	TransactionID  string `json:"transaction_id"`
	BlockTimestamp int64  `json:"block_timestamp"`
	From           string `json:"from"`
	To             string `json:"to"`
	Type           string `json:"type"`
	Value          string `json:"value"`
}

var rpcClient *grpcs.Client

func getClient() (*grpcs.Client, error) {
	if rpcClient != nil {
		return rpcClient, nil
	}

	grpcHost := viper.GetString("tron.grpcHost")
	if grpcHost == "" {
		grpcHost = "grpc.trongrid.io:50051"
	}

	c, err := grpcs.NewClient(grpcHost)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}

	rpcClient = c

	return c, err
}

func TransferTrx(from string, to string, amount int64, key string) (bool, string) {
	c, err := getClient()
	if err != nil {
		fmt.Println(err)
	}

	tx, err := c.Transfer(from, to, amount)
	if err != nil {
		fmt.Println(err)
		return false, ""
	}
	signTx, err := sign.SignTransaction(tx.Transaction, key)
	if err != nil {
		fmt.Println(err)
		return false, ""
	}
	err = c.BroadcastTransaction(signTx)
	if err != nil {
		fmt.Println(err)
		return false, ""
	}

	return true, common.BytesToHexString(tx.GetTxid())
}

func TransferTrc20(contractAddress string, from string, to string, amount int64, key string) (bool, string, *core.Transaction) {
	if len(key) != 64 {
		logrus.Error("key not valid:", len(key), "!=64")
		return false, "", nil
	}

	// grpcHost := viper.GetString("tron.grpcHost")
	c, err := getClient()
	if err != nil {
		logrus.Error(err)
		return false, "", nil
	}

	amountBig := big.NewInt(amount)
	tx, err := c.TransferTrc20(from, to, contractAddress, amountBig, 10000000)
	if err != nil {
		logrus.Error(err)
		return false, "", nil
	}

	signTx, err := sign.SignTransaction(tx.Transaction, key)
	if err != nil {
		logrus.Error(err)
		return false, "", nil
	}

	// err = c.BroadcastTransaction(signTx)
	// if err != nil {
	// 	fmt.Println(err)
	// 	return false, nil
	// }

	return true, common.BytesToHexString(tx.GetTxid()), signTx
}

func BroadcastTransaction(signTx *core.Transaction) error {
	c, err := getClient()
	if err != nil {
		logrus.Error(err)
		return err
	}

	return c.BroadcastTransaction(signTx)
}

func GetBalance(addr string) (int64, error) {
	c, err := getClient()
	if err != nil {
		return 0, err
	}

	acc, err := c.GetTrxBalance(addr)
	if err != nil {
		return 0, err
	}

	// d, _ := json.Marshal(acc)
	// fmt.Println(string(d))

	return acc.GetBalance(), nil

}

func GetTrc20BalanceByHash(hash string) (int64, error) {
	type HashInfoResp struct {
		Block        int64  `json:"block"`
		Confirmed    bool   `json:"confirmed"`
		Revert       bool   `json:"revert"`
		ContractRet  string `json:"contractRet"`
		ContractData struct {
			Amount       int    `json:"amount"`
			OwnerAddress string `json:"owner_address"`
			ToAddress    string `json:"to_address"`
		} `json:"contractData"`
		Trc20TransferInfo []struct {
			IconURL         string `json:"icon_url"`
			Symbol          string `json:"symbol"`
			Level           string `json:"level"`
			Decimals        int    `json:"decimals"`
			Name            string `json:"name"`
			ToAddress       string `json:"to_address"`
			ContractAddress string `json:"contract_address"`
			Type            string `json:"type"`
			Vip             bool   `json:"vip"`
			TokenType       string `json:"tokenType"`
			FromAddress     string `json:"from_address"`
			AmountStr       string `json:"amount_str"`
		} `json:"trc20TransferInfo"`
	}

	reqUrl := "https://apilist.tronscan.org/api/transaction-info?hash=" + hash + "&t=" + fmt.Sprintf("%d", time.Now().Unix())
	logrus.Info("req url:", reqUrl)

	res, err := http.Get(reqUrl)
	if err != nil {
		logrus.Error(err)
		return 0, err
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		logrus.Error(err)
		return 0, err
	}

	defer res.Body.Close()

	hashResp := HashInfoResp{}
	err = json.Unmarshal(body, &hashResp)
	if err != nil {
		logrus.Error(err, string(body))
		return 0, err
	}

	if hashResp.Block == 0 {
		logrus.Error("block id未生效:", string(body))
		return 0, errors.New("not valid")
	}

	if hashResp.ContractRet != "SUCCESS" {
		return 0, errors.New("transfer failed")
	}

	if !hashResp.Confirmed {
		return 0, errors.New("not confirm")
	}

	if len(hashResp.Trc20TransferInfo) != 1 {
		return 0, errors.New("data not valid")
	}

	val, err := strconv.ParseInt(hashResp.Trc20TransferInfo[0].AmountStr, 10, 64)
	if err != nil {
		return 0, err
	}

	return val, nil
}

func GetTrc20Balance(addr string, contractAddress string) (int64, error) {
	c, err := getClient()
	if err != nil {
		return 0, err
	}

	amount, err := c.GetTrc20Balance(addr, contractAddress)
	if err != nil {
		return 0, err
	}

	val, err := strconv.ParseInt(amount.String(), 10, 64)
	if err != nil {
		return 0, err
	}

	return val, nil
}

func IsTransactionSucc_(txid string) (bool, error) {
	c, err := getClient()
	if err != nil {
		logrus.Error(err)
		return false, err
	}

	txInfo, err := c.GRPC.GetTransactionByID(txid)
	if err != nil {
		logrus.Error(err)
		return false, err
	}

	if len(txInfo.Ret) == 0 {
		//半小时也没有，认为失败
		if txInfo.GetRawData().Timestamp-time.Now().Unix() > 30*60 {
			return false, nil
		}

		logrus.Error("len(txInfo.Ret) == 0")
		return false, errors.New("no result")
	}

	if txInfo.Ret[0].ContractRet == core.Transaction_Result_DEFAULT {
		return false, errors.New("wait contract result")
	}

	return txInfo.Ret[0].Ret == core.Transaction_Result_SUCESS && txInfo.Ret[0].ContractRet == core.Transaction_Result_SUCCESS,
		nil
}

func IsTransactionSucc(hash string) (bool, error) {
	c, err := getClient()
	if err != nil {
		logrus.Error(err)
		return false, err
	}

	body, err := c.GRPC.GetTransactionInfoByID(hash)
	if err != nil {
		logrus.Error(err)
		return false, err
	}

	switch body.Receipt.GetResult().String() {
	case "SUCCESS":
		return true, nil
	case "REVERT":
		return false, nil
	default:
		return false, errors.New("no result")
	}
}

func CheckToComtainSince(addr string, val int64, since int64) (bool, string, error) {
	fp := ""
	for {
		his, retfp, err := GetTxHistoryByAddr(addr, fp, 20)
		if err != nil {
			fmt.Println(err)
			return false, "", err
		}

		if len(his) == 0 {
			break
		}

		for _, info := range his {
			if info.BlockTimestamp < since {
				continue
			}

			value, err := strconv.ParseInt(info.Value, 10, 64)
			if err != nil {
				fmt.Println(err)
				continue
			}

			if value != val {
				continue
			}

			return true, info.From, nil
		}

		if retfp == "" {
			break
		}

		fp = retfp
	}

	return false, "", nil
}

func GetTxHistoryByAddr(addr string, fingerprint string, limit int) ([]txInfoItem, string, error) {
	apiHost := viper.GetString("tron.apiHost")
	contractAddr := viper.GetString("contractaddr")
	if contractAddr == "" {
		logrus.Fatal("contractaddr is empty")
		return nil, "", errors.New("contractaddr is empty")
	}

	reqUrl := fmt.Sprintf(`https://%s/v1/accounts/%s/transactions/trc20?limit=%d&contract_address=%s&only_confirmed=true&only_to=true&order_by=block_timestamp,desc`,
		apiHost, addr, limit, contractAddr)

	logrus.Info("reqUrl:", reqUrl)

	if fingerprint != "" {
		reqUrl = reqUrl + "&fingerprint=" + fingerprint
	}

	resp, err := http.Get(reqUrl)
	if err != nil {
		logrus.Error(err, " ", reqUrl)
		return nil, "", err
	}

	type Meta struct {
		Fingerprint string `json:"fingerprint"`
	}

	type DataInfo struct {
		Data []txInfoItem `json:"data"`
		Meta Meta         `json:"meta"`
	}

	dataInfo := DataInfo{}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		logrus.Error(err, " ", reqUrl)
		return nil, "", err
	}

	defer resp.Body.Close()

	err = json.Unmarshal(body, &dataInfo)
	if err != nil {
		logrus.Error(err, " ", reqUrl)
		return nil, "", err
	}

	if dataInfo.Data == nil {
		return nil, "", errors.New(string(body))
	}

	resultList := make([]txInfoItem, 0)
	for _, item := range dataInfo.Data {
		if item.Type != "Transfer" {
			continue
		}

		resultList = append(resultList, item)
	}

	return resultList, dataInfo.Meta.Fingerprint, nil
}
