package apinode

import (
	"encoding/hex"
	"encoding/json"

	"github.com/vapor/blockchain/txbuilder"
	"github.com/vapor/consensus"
	"github.com/vapor/errors"
	"github.com/vapor/protocol/bc"
	"github.com/vapor/protocol/bc/types"
)

type SpendAccountAction struct {
	AccountID string `json:"account_id"`
	*bc.AssetAmount
}

func (s *SpendAccountAction) MarshalJSON() ([]byte, error) {
	return json.Marshal(&struct {
		Type      string `json:"type"`
		AccountID string `json:"account_id"`
		*bc.AssetAmount
	}{
		Type:        "spend_account",
		AccountID:   s.AccountID,
		AssetAmount: s.AssetAmount,
	})
}

type ControlAddressAction struct {
	Address string `json:"address"`
	*bc.AssetAmount
}

func (c *ControlAddressAction) MarshalJSON() ([]byte, error) {
	return json.Marshal(&struct {
		Type    string `json:"type"`
		Address string `json:"address"`
		*bc.AssetAmount
	}{
		Type:        "control_address",
		Address:     c.Address,
		AssetAmount: c.AssetAmount,
	})
}

type RetireAction struct {
	*bc.AssetAmount
	Arbitrary []byte
}

func (r *RetireAction) MarshalJSON() ([]byte, error) {
	return json.Marshal(&struct {
		Type      string `json:"type"`
		Arbitrary string `json:"arbitrary"`
		*bc.AssetAmount
	}{
		Type:        "retire",
		Arbitrary:   hex.EncodeToString(r.Arbitrary),
		AssetAmount: r.AssetAmount,
	})
}

func (n *Node) BatchSendBTM(accountID, password string, outputs map[string]uint64, memo []byte) (string, error) {
	totalBTM := uint64(1000000)
	actions := []interface{}{}
	if len(memo) > 0 {
		actions = append(actions, &RetireAction{
			Arbitrary:   memo,
			AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: 1},
		})
	}

	for address, amount := range outputs {
		actions = append(actions, &ControlAddressAction{
			Address:     address,
			AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: amount},
		})
		totalBTM += amount
	}

	actions = append(actions, &SpendAccountAction{
		AccountID:   accountID,
		AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: totalBTM},
	})

	tpl, err := n.buildTx(actions)
	if err != nil {
		return "", err
	}

	tpl, err = n.signTx(tpl, password)
	if err != nil {
		return "", err
	}

	return n.SubmitTx(tpl.Transaction)
}

type buildTxReq struct {
	Actions []interface{} `json:"actions"`
}

func (n *Node) buildTx(actions []interface{}) (*txbuilder.Template, error) {
	url := "/build-transaction"
	payload, err := json.Marshal(&buildTxReq{Actions: actions})
	if err != nil {
		return nil, errors.Wrap(err, "Marshal spend request")
	}

	result := &txbuilder.Template{}
	return result, n.request(url, payload, result)
}

func (n *Node) BuildChainTxs(actions []interface{}) ([]*txbuilder.Template, error) {
	url := "/build-chain-transactions"

	payload, err := json.Marshal(&buildTxReq{Actions: actions})
	if err != nil {
		return nil, errors.Wrap(err, "Marshal spend request")
	}

	result := []*txbuilder.Template{}
	return result, n.request(url, payload, &result)
}

type signTxReq struct {
	Tx       *txbuilder.Template `json:"transaction"`
	Password string              `json:"password"`
}

type signTxResp struct {
	Tx           *txbuilder.Template `json:"transaction"`
	SignComplete bool                `json:"sign_complete"`
}

func (n *Node) signTx(tpl *txbuilder.Template, password string) (*txbuilder.Template, error) {
	url := "/sign-transaction"
	payload, err := json.Marshal(&signTxReq{Tx: tpl, Password: password})
	if err != nil {
		return nil, errors.Wrap(err, "json marshal")
	}

	resp := &signTxResp{}
	if err := n.request(url, payload, resp); err != nil {
		return nil, err
	}

	if !resp.SignComplete {
		return nil, errors.New("sign fail")
	}

	return resp.Tx, nil
}

type signTxsReq struct {
	Txs      []*txbuilder.Template `json:"transactions"`
	Password string                `json:"password"`
}

type signTxsResp struct {
	Txs          []*txbuilder.Template `json:"transaction"`
	SignComplete bool                  `json:"sign_complete"`
}

func (n *Node) SignTxs(tpls []*txbuilder.Template, password string) ([]*txbuilder.Template, error) {
	url := "/sign-transactions"
	payload, err := json.Marshal(&signTxsReq{Txs: tpls, Password: password})
	if err != nil {
		return nil, errors.Wrap(err, "json marshal")
	}

	resp := &signTxsResp{}
	if err := n.request(url, payload, resp); err != nil {
		return nil, err
	}

	if !resp.SignComplete {
		return nil, errors.New("sign fail")
	}

	return resp.Txs, nil
}

type submitTxReq struct {
	Tx *types.Tx `json:"raw_transaction"`
}

type submitTxResp struct {
	TxID string `json:"tx_id"`
}

func (n *Node) SubmitTx(tx *types.Tx) (string, error) {
	url := "/submit-transaction"
	payload, err := json.Marshal(submitTxReq{Tx: tx})
	if err != nil {
		return "", errors.Wrap(err, "json marshal")
	}

	res := &submitTxResp{}
	return res.TxID, n.request(url, payload, res)
}

type submitTxsReq struct {
	Txs []*types.Tx `json:"raw_transactions"`
}

type submitTxsResp struct {
	TxsID []string `json:"tx_id"`
}

func (n *Node) SubmitTxs(txs []*types.Tx) ([]string, error) {
	url := "/submit-transactions"
	payload, err := json.Marshal(submitTxsReq{Txs: txs})
	if err != nil {
		return []string{}, errors.Wrap(err, "json marshal")
	}

	res := &submitTxsResp{}
	return res.TxsID, n.request(url, payload, res)
}
