package xzc

import (
	"blockbook/bchain"
	"blockbook/bchain/coins/btc"
	"encoding/hex"
	"encoding/json"

	"github.com/golang/glog"
	"github.com/juju/errors"
)

type ZcoinRPC struct {
	*btc.BitcoinRPC
}

func NewZcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
	// init base implementation
	bc, err := btc.NewBitcoinRPC(config, pushHandler)
	if err != nil {
		return nil, err
	}

	// init zcoin implementation
	zc := &ZcoinRPC{
		BitcoinRPC: bc.(*btc.BitcoinRPC),
	}

	zc.ChainConfig.Parse = true
	zc.ChainConfig.SupportsEstimateFee = true
	zc.ChainConfig.SupportsEstimateSmartFee = false
	zc.ParseBlocks = true
	zc.RPCMarshaler = btc.JSONMarshalerV1{}

	return zc, nil
}

func (zc *ZcoinRPC) Initialize() error {
	chainName, err := zc.GetChainInfoAndInitializeMempool(zc)
	if err != nil {
		return err
	}

	params := GetChainParams(chainName)

	// always create parser
	zc.Parser = NewZcoinParser(params, zc.ChainConfig)

	// parameters for getInfo request
	if params.Net == MainnetMagic {
		zc.Testnet = false
		zc.Network = "livenet"
	} else {
		zc.Testnet = true
		zc.Network = "testnet"
	}

	glog.Info("rpc: block chain ", params.Name)

	return nil
}

func (zc *ZcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
	var err error

	if hash == "" {
		hash, err = zc.GetBlockHash(height)
		if err != nil {
			return nil, err
		}
	}

	// optimization
	if height > 0 {
		return zc.GetBlockWithoutHeader(hash, height)
	}

	header, err := zc.GetBlockHeader(hash)
	if err != nil {
		return nil, err
	}

	data, err := zc.GetBlockRaw(hash)
	if err != nil {
		return nil, err
	}

	block, err := zc.Parser.ParseBlock(data)
	if err != nil {
		return nil, errors.Annotatef(err, "hash %v", hash)
	}

	block.BlockHeader = *header

	return block, nil
}

func (zc *ZcoinRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
	glog.V(1).Info("rpc: getblock (verbosity=true) ", hash)

	res := btc.ResGetBlockInfo{}
	req := cmdGetBlock{Method: "getblock"}
	req.Params.BlockHash = hash
	req.Params.Verbosity = true
	err := zc.Call(&req, &res)

	if err != nil {
		return nil, errors.Annotatef(err, "hash %v", hash)
	}
	if res.Error != nil {
		if btc.IsErrBlockNotFound(res.Error) {
			return nil, bchain.ErrBlockNotFound
		}
		return nil, errors.Annotatef(res.Error, "hash %v", hash)
	}
	return &res.Result, nil
}

func (zc *ZcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) {
	data, err := zc.GetBlockRaw(hash)
	if err != nil {
		return nil, err
	}

	block, err := zc.Parser.ParseBlock(data)
	if err != nil {
		return nil, errors.Annotatef(err, "%v %v", height, hash)
	}

	block.BlockHeader.Hash = hash
	block.BlockHeader.Height = height

	return block, nil
}

func (zc *ZcoinRPC) GetBlockRaw(hash string) ([]byte, error) {
	glog.V(1).Info("rpc: getblock (verbosity=false) ", hash)

	res := btc.ResGetBlockRaw{}
	req := cmdGetBlock{Method: "getblock"}
	req.Params.BlockHash = hash
	req.Params.Verbosity = false
	err := zc.Call(&req, &res)

	if err != nil {
		return nil, errors.Annotatef(err, "hash %v", hash)
	}
	if res.Error != nil {
		if btc.IsErrBlockNotFound(res.Error) {
			return nil, bchain.ErrBlockNotFound
		}
		return nil, errors.Annotatef(res.Error, "hash %v", hash)
	}
	return hex.DecodeString(res.Result)
}

func (zc *ZcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
	glog.V(1).Info("rpc: getrawtransaction nonverbose ", txid)

	res := btc.ResGetRawTransactionNonverbose{}
	req := cmdGetRawTransaction{Method: "getrawtransaction"}
	req.Params.Txid = txid
	req.Params.Verbose = 0
	err := zc.Call(&req, &res)
	if err != nil {
		return nil, errors.Annotatef(err, "txid %v", txid)
	}
	if res.Error != nil {
		if btc.IsMissingTx(res.Error) {
			return nil, bchain.ErrTxNotFound
		}
		return nil, errors.Annotatef(res.Error, "txid %v", txid)
	}
	data, err := hex.DecodeString(res.Result)
	if err != nil {
		return nil, errors.Annotatef(err, "txid %v", txid)
	}
	tx, err := zc.Parser.ParseTx(data)
	if err != nil {
		return nil, errors.Annotatef(err, "txid %v", txid)
	}
	return tx, nil
}

func (zc *ZcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
	r, err := zc.getRawTransaction(txid)
	if err != nil {
		return nil, err
	}

	tx, err := zc.Parser.ParseTxFromJson(r)
	tx.CoinSpecificData = r
	if err != nil {
		return nil, errors.Annotatef(err, "txid %v", txid)
	}

	return tx, nil
}

func (zc *ZcoinRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
	if csd, ok := tx.CoinSpecificData.(json.RawMessage); ok {
		return csd, nil
	}
	return zc.getRawTransaction(tx.Txid)
}

func (zc *ZcoinRPC) getRawTransaction(txid string) (json.RawMessage, error) {
	glog.V(1).Info("rpc: getrawtransaction ", txid)

	res := btc.ResGetRawTransaction{}
	req := cmdGetRawTransaction{Method: "getrawtransaction"}
	req.Params.Txid = txid
	req.Params.Verbose = 1
	err := zc.Call(&req, &res)

	if err != nil {
		return nil, errors.Annotatef(err, "txid %v", txid)
	}
	if res.Error != nil {
		if btc.IsMissingTx(res.Error) {
			return nil, bchain.ErrTxNotFound
		}
		return nil, errors.Annotatef(res.Error, "txid %v", txid)
	}
	return res.Result, nil
}

type cmdGetBlock struct {
	Method string `json:"method"`
	Params struct {
		BlockHash string `json:"blockhash"`
		Verbosity bool   `json:"verbosity"`
	} `json:"params"`
}

type cmdGetRawTransaction struct {
	Method string `json:"method"`
	Params struct {
		Txid    string `json:"txid"`
		Verbose int    `json:"verbose"`
	} `json:"params"`
}
