package rpc

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"

	"gitlab.33.cn/link33/sidecar-client-chain33/util"

	types33 "github.com/33cn/chain33/types"

	"gitlab.33.cn/link33/sidecar-client-chain33/crypto"
	"go.starlark.net/lib/proto"
)

// Client a object of json client
type Client struct {
	url       string
	Prefix    string
	tlsVerify bool
	client    *http.Client
}

func addPrefix(prefix, name string) string {
	if strings.Contains(name, ".") {
		return name
	}
	return prefix + "." + name
}

// NewClient produce a json object
func NewClient(prefix, url string) (*Client, error) {
	return newClient(prefix, url, false)
}

// New produce a json client by Prefix and url
func newClient(prefix, url string, tlsVerify bool) (*Client, error) {
	httpcli := http.DefaultClient
	if strings.Contains(url, "https") { // 暂不校验tls证书
		httpcli = &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: !tlsVerify}}}
	}
	return &Client{
		url:       url,
		Prefix:    prefix,
		tlsVerify: tlsVerify,
		client:    httpcli,
	}, nil
}

type clientRequest struct {
	Method string         `json:"method"`
	Params [1]interface{} `json:"params"`
	ID     uint64         `json:"id"`
}

type clientResponse struct {
	ID     uint64           `json:"id"`
	Result *json.RawMessage `json:"result"`
	Error  interface{}      `json:"error"`
}

func (client *Client) Call(method string, params, resp interface{}) error {
	method = addPrefix(client.Prefix, method)
	req := &clientRequest{}
	req.Method = method
	req.Params[0] = params
	data, err := json.Marshal(req)
	if err != nil {
		return err
	}
	postresp, err := client.client.Post(client.url, "application/json", bytes.NewBuffer(data))
	if err != nil {
		return err
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
		}
	}(postresp.Body)
	b, err := ioutil.ReadAll(postresp.Body)
	if err != nil {
		return err
	}
	cresp := &clientResponse{}
	err = json.Unmarshal(b, &cresp)
	if err != nil {
		return err
	}
	if cresp.Error != nil {
		x, ok := cresp.Error.(string)
		if !ok {
			return fmt.Errorf("invalid error %v", cresp.Error)
		}
		if x == "" {
			x = "unspecified error"
		}
		return fmt.Errorf(x)
	}
	if cresp.Result == nil {
		return errors.New("empty result")
	}
	if msg, ok := resp.(proto.Message); ok {
		var str json.RawMessage
		err = json.Unmarshal(*cresp.Result, &str)
		if err != nil {
			return err
		}
		b, err := str.MarshalJSON()
		if err != nil {
			return err
		}
		err = json.Unmarshal(b, &msg)
		if err != nil {
			fmt.Println("err", err)
			return err
		}
		return nil
	}
	return json.Unmarshal(*cresp.Result, resp)
}

type ParseFunc func(result json.RawMessage) (interface{}, error)

// CallBack 回调函数，用于自定义解析返回得result数据
func (client *Client) CallBack(method string, params interface{}, parseFunc ParseFunc) (interface{}, error) {
	method = addPrefix(client.Prefix, method)
	req := &clientRequest{}
	req.Method = method
	req.Params[0] = params
	data, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}
	postresp, err := client.client.Post(client.url, "application/json", bytes.NewBuffer(data))
	if err != nil {
		return nil, err
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
		}
	}(postresp.Body)
	b, err := ioutil.ReadAll(postresp.Body)
	if err != nil {
		return nil, err
	}
	cresp := &clientResponse{}
	err = json.Unmarshal(b, &cresp)
	if err != nil {
		return nil, err
	}
	if cresp.Error != nil {
		x, ok := cresp.Error.(string)
		if !ok {
			return nil, fmt.Errorf("invalid error %v", cresp.Error)
		}
		if x == "" {
			x = "unspecified error"
		}
		return nil, fmt.Errorf(x)
	}
	if cresp.Result == nil {
		return nil, errors.New("empty result")
	}
	return parseFunc(*cresp.Result)
}

func (client *Client) CreateTransaction(execer, actionName string, payload types33.Message) (*types33.Transaction, error) {
	var res string
	type CreateTxIn struct {
		Execer     string          `json:"execer"`
		ActionName string          `json:"actionName"`
		Payload    json.RawMessage `json:"payload"`
	}
	params := &CreateTxIn{
		Execer:     execer,
		ActionName: actionName,
		Payload:    types33.MustPBToJSON(payload),
	}
	err := client.Call("Chain33.CreateTransaction", params, &res)
	if err != nil {
		return nil, err
	}

	data, err := util.FromHex(res)
	if err != nil {
		return nil, err
	}
	tx := &types33.Transaction{}
	err = types33.Decode(data, tx)
	if err != nil {
		return nil, err
	}

	return tx, nil
}

// SendTransaction 发送交易
func (client *Client) SendTransaction(signedTx string) (string, error) {
	var res string
	send := &RawParm{
		Token: "BTY",
		Data:  signedTx,
	}
	err := client.Call("Chain33.SendTransaction", send, &res)
	if err != nil {
		return "", err
	}

	return res, nil
}

func (client *Client) SendTransactionSync(privkey string, tx *types33.Transaction) (*Response, error) {
	// 签名
	hexbytes, _ := util.FromHex(privkey)
	_, err := Sign(tx, hexbytes, crypto.SECP256K1, nil)
	if err != nil {
		return nil, err
	}
	signedTx := util.ToHexPrefix(types33.Encode(tx))
	var res string
	send := &RawParm{
		Token: "BTY",
		Data:  signedTx,
	}
	err = client.Call("Chain33.SendTransactionSync", send, &res)
	if err != nil {
		return nil, err
	}
	return &Response{OK: true, Message: res, Data: types33.Encode(tx)}, nil
}

// QueryTransaction 查询交易
func (client *Client) QueryTransaction(hash string) (*TransactionDetail, error) {
	query := QueryParm{
		Hash: hash,
	}
	var detail TransactionDetail
	err := client.Call(Chain33_QueryTransaction, query, &detail)
	if err != nil {
		return nil, err
	}

	return &detail, nil
}

func (client *Client) QueryLastHeader() (*Header, error) {
	var res Header
	err := client.Call(Chain33_GetLastHeader, nil, &res)
	if err != nil {
		return nil, err
	}
	return &res, nil
}

func (client *Client) QueryBlockHash(height int64) (*ReplyHash, error) {
	var res ReplyHash
	params := types33.ReqInt{
		Height: height,
	}
	err := client.Call(Chain33_GetBlockHash, params, &res)
	if err != nil {
		return nil, err
	}
	return &res, nil
}

func (client *Client) QueryHeadersByHeight(height int64) (*Headers, error) {
	var res Headers
	params := BlockParam{
		Start:    height - 1,
		End:      height - 1,
		Isdetail: false,
	}
	err := client.Call(Chain33_GetHeaders, params, &res)
	if err != nil {
		return nil, err
	}
	return &res, nil
}

func (client *Client) QueryIsSync() (bool, error) {
	var res bool
	err := client.Call(Para_IsSync, nil, &res)
	if err != nil {
		return false, err
	}
	return res, nil
}

func (client *Client) QueryMainNetIsSync() (bool, error) {
	var res bool
	err := client.Call(Chain33_IsSync, nil, &res)
	if err != nil {
		return false, err
	}
	return res, nil
}

func (client *Client) QueryNetInfo() (*NodeNetinfo, error) {
	var res NodeNetinfo
	params := types33.ReqNil{}
	err := client.Call(Chain33_GetNetInfo, params, &res)
	if err != nil {
		return nil, err
	}
	return &res, nil
}

func (client *Client) QueryMempool() (*ReplyTxList, error) {
	var res ReplyTxList
	params := types33.ReqNil{}
	err := client.Call(Chain33_GetMempool, params, &res)
	if err != nil {
		return nil, err
	}
	return &res, nil
}

// QueryBlockInfo 获取区块信息
func (client *Client) QueryBlockInfo(start, end int64, isDetail bool) (*BlockDetails, error) {
	var res BlockDetails
	params := BlockParam{Start: start, End: end, Isdetail: isDetail}
	err := client.Call(Chain33_GetBlocks, params, &res)
	if err != nil {
		return nil, err
	}
	return &res, nil
}
