package dcrcashRpc

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"strconv"
)

//CoinDefaultProto  default coin protp
const CoinDefaultProto string = "http"

//CoinDefaultProto  default coin protp
const CoinDefaultjsonrpc string = "1.0"

//CoinDefaultid  default coin id
const CoinDefaultid string = "curltest"

// litecoin Host address
var Host string = "localhost"

//litecoin  port
var Port int = 9110

// litecoin address
var User string = "rpcuser"

//litecoin  Password
var Password string = "rpcpass"

type Request struct {
	Jsonrpc string            `json:"jsonrpc"`
	Method  string            `json:"method"`
	Params  []json.RawMessage `json:"params"`
	ID      interface{}       `json:"id"`
}

type Coin struct {
	// Configuration options
	username string
	password string
	proto    string
	host     string
	port     int
	url      string
	jsonrpc  string
	// rawResponse string
	responseData []byte
	id           string
	client       *http.Client
}

func NewCoin(coinUser, coinPasswd, coinHost, coinURL string, coinPort int) (cn *Coin, err error) {
	cn = &Coin{
		username: coinUser,
		password: coinPasswd,
		host:     coinHost,
		port:     coinPort,
		url:      coinURL,
		proto:    CoinDefaultProto,
		jsonrpc:  CoinDefaultjsonrpc,
		id:       CoinDefaultid,
	}
	if len(coinHost) == 0 {
		cn.host = "localhost"
	}
	if coinPort < 0 || coinPort > 65535 {
		cn.port = 9110
	}
	cn.client = &http.Client{}
	cn.client.Transport = &http.Transport{}
	//cn.client, err = newHTTPCliet()

	return cn, nil
}

func newHTTPCliet() (*http.Client, error) {
	var dial func(network, addr string) (net.Conn, error)

	var tlsConfig *tls.Config

	pem, err := ioutil.ReadFile("/home/hdb/.dcrwallet/rpc.cert")
	if err != nil {
		return nil, err
	}

	pool := x509.NewCertPool()
	if ok := pool.AppendCertsFromPEM(pem); !ok {
		return nil, fmt.Errorf("invalid certificate file!!")
	}
	tlsConfig = &tls.Config{
		RootCAs:            pool,
		InsecureSkipVerify: false,
	}
	//fmt.Println(string(pem), tlsConfig.InsecureSkipVerify)
	fmt.Fprintf(os.Stderr, "%s\n", string(pem), tlsConfig.InsecureSkipVerify)
	client := http.Client{
		Transport: &http.Transport{
			Dial:            dial,
			TLSClientConfig: tlsConfig,
		},
	}
	return &client, nil
}

func (cn *Coin) access(jbuf []byte) (err error) {
	if cn.client == nil {
		err = errors.New("http client error")
		return
	}

	cn.responseData = nil

	var (
		httpRequest *http.Request
		resp        *http.Response
	)
	fmt.Println(string(jbuf))
	addr := cn.proto + "://" + cn.host + ":" + strconv.Itoa(cn.port) + "/" + cn.url

	httpRequest, err = http.NewRequest("POST", addr, bytes.NewReader(jbuf))
	if err != nil {
		fmt.Println(" http.NewRequest:", err.Error())
		return
	}
	httpRequest.Close = true
	httpRequest.Header.Set("Content-Type", "application/json")

	httpRequest.SetBasicAuth(cn.username, cn.password)

	if resp, err = cn.client.Do(httpRequest); err != nil {
		fmt.Println(" cn.client.Do fail:", err.Error())
		return
	}

	if resp.StatusCode != http.StatusOK {
		err = errors.New(resp.Status)
		fmt.Println(" resp.StatusCode:", err.Error())
		//return
	}

	defer resp.Body.Close()
	if cn.responseData, err = ioutil.ReadAll(resp.Body); err != nil {
		fmt.Println("ioutil.ReadAll(resp.Body):", err.Error())
		return
	}
	if len(cn.responseData) == 0 {
		err = errors.New("response data is empty")
		return
	}
	//fmt.Println("cn.responseData:", string(cn.responseData))
	return
}

//Call run RPC command
func RunCall(method string, args ...interface{}) (data []byte, err error) {
	if method == "" {
		err = errors.New("method is not set")
		return
	}

	//
	dcrCoin, err := NewCoin(User, Password, Host, "", Port)
	if err != nil {
		fmt.Println("dcrCoin.NewCoin fail", err.Error())
		return nil, err
	}
	marsh1cmd, err := Marsha1cmd(method, args...)
	if err != nil {
		fmt.Println("dcrcashRpc RunCall Marsh1cmd fail", err.Error())
		return nil, err
	}
	if err = dcrCoin.access(marsh1cmd); err == nil {
		data = dcrCoin.responseData
	}
	return
}

func InitRpcConf(rpcuser string, rpcpassword string, rpchost string, rpcport int) bool {
	fmt.Println("default rpc config:", rpcuser, rpcpassword, Host, Port)
	if len(rpcuser) != 0 {
		User = rpcuser
	}

	if len(rpcpassword) != 0 {
		Password = rpcpassword
	}
	if len(rpchost) != 0 {
		Host = rpchost
	}
	if rpcport != 0 {
		Port = rpcport
	}

	fmt.Println("current rpc config:", User, Password, Host, Port)
	return true
}

func Marsha1cmd(method string, args ...interface{}) ([]byte, error) {
	/*rt := reflect.TypeOf(cmd)
	method, ok := concreateTypeToMethod[rt]
	if !ok {
		str := fmt.Sprintf("%q is not registered", method)
		return nil, error.Error(str)
	}

	rv := reflect.ValueOf(cmd)
	if rv.IsNil() {
		str := "the specified command is nil"
		return nil, error.Error(str)
	}

	params := makeParams(rt.Elem(), rv.Elem())*/
	if method == "" {
		str := fmt.Sprintf("%q is not method", method)
		return nil, errors.New(str)
	}
	params := make([]interface{}, 0, len(args))
	for _, v := range args {
		params = append(params, v)
	}

	//params := cmd["params"]
	//params = []interface{}(Params)
	rawCmd, err := NewRequest(1, method, params)
	if err != nil {
		return nil, err
	}
	return json.Marshal(rawCmd)
}

func NewRequest(id interface{}, method string, params []interface{}) (*Request, error) {
	rawParams := make([]json.RawMessage, 0, len(params))
	for _, param := range params {
		marshalledparam, err := json.Marshal(param)
		if err != nil {
			return nil, err
		}
		rawMessage := json.RawMessage(marshalledparam)
		rawParams = append(rawParams, rawMessage)
	}

	return &Request{
		Jsonrpc: CoinDefaultjsonrpc,
		ID:      id,
		Method:  method,
		Params:  rawParams,
	}, nil
}

/*func makeParams(rt reflect.Type, rv reflect.Value) []interface{} {
	numFields := rt.NumField()
	params := make([]interface{}, 0, numFields)
	for i := 0; i < numFields; i++ {
		rtf := rt.Field(i)
		rvf := rv.Field(i)
		if rtf.Type.Kind() == reflect.Ptr {
			if rvf.IsNil() {
				break
			}
			rvf.Elem()
		}
		params = append(params, rvf.Interface())
	}
	return params
}*/
