package blockchain

import (
	"bytes"
	"encoding/base64"
	rawjson "encoding/json"
	"fmt"
	"github.com/beego/beego/v2/client/httplib"
	"github.com/linmadan/egglib-go/utils/json"
	"net/http"
	"net/http/httputil"
	"sort"
	"time"
)

type (
	BSNBlockChain struct {
		PublicPem      []byte
		PrivatePem     []byte
		PublicKey      string
		Host           string
		EnableDebugLog bool
	}
	UpToChainRequest struct {
		// 上链数据的数据库、数据表等的标识值  (非必填)
		InnerDBTable string `json:"innerDBTable,omitempty"`
		// 上链数据的唯一标识主键值 (非必填)
		InnerPrimaryKey string `json:"innerPrimaryKey,omitempty"`
		// 上链记录的一个标记值(IssueId), 数据溯源出所有相关事件内容,例如快递单号,过滤出该快递的所有相关事件内容并用于展示 (非必填)
		InnerPrimaryIssueId string `json:"innerPrimaryIssueId,omitempty"`
		// 作用与key1相同 (非必填)
		InnerSecondIssueId string `json:"innerSecondIssueId,omitempty"`
		// 数据原文 (必填)
		Value string `json:"value,omitempty"`
		// 数据描述: 对value的描述,无论needHash为何值,本字段均会原文存储到链上
		Desc string `json:"desc,omitempty"`
		// 是否哈希: true: 需要哈希,会将value进行hash上链,false:不需要哈希,明文上链,链上所有用户都可看到明文,默认false
		NeedHash bool `json:"needHash"`
	}
	UpToChainResponse string

	GetTokenRequest struct {
		// 操作类型:
		//1-交易哈希溯源
		//2-溯源ID溯源
		//3-验真
		Type int `json:"type"`
		// type为1或者3时必填
		TsTxId string `json:"tsTxId,omitempty"`
		// type为2时必填
		IssueId string `json:"issueId,omitempty"`
		// type为3时必填
		Value string `json:"value,omitempty"`
		// 当type=1或者2必填,为false只显示密文,为true溯源才会显示原文
		ShowValue bool `json:"showValue"`
	}
	GetTokenResponse struct {
		Token string `json:"token"`
	}

	Response struct {
		Data    rawjson.RawMessage `json:"data"`
		Code    int                `json:"code"`
		Message string             `json:"message"`
	}
)

// 上链
func (c *BSNBlockChain) UpToChain(options *UpToChainOptions) (*UpToChainResponse, error) {
	req, err := c.MakeRequest(options, "/chainApi/upToChain", "upToChain", http.MethodPost)
	if err != nil {
		return nil, err
	}

	var upToChainResponse UpToChainResponse
	_, err = c.HandlerResponse(req, &upToChainResponse)

	return &upToChainResponse, err
}

// 浏览器溯源验真申请
func (c *BSNBlockChain) GetToken(options *GetTokenRequest) (*GetTokenResponse, error) {
	req, err := c.MakeRequest(options, "/chainApi/getToken", "getToken", http.MethodPost)
	if err != nil {
		return nil, err
	}
	var getTokenResponse = GetTokenResponse{}
	_, err = c.HandlerResponse(req, &getTokenResponse)
	return &getTokenResponse, err
}

// 签名
func (c *BSNBlockChain) Signature(body map[string]interface{}, method string) (string, error) {
	var keys []string
	for key, _ := range body {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	encryptString := bytes.NewBuffer(nil)
	for i := range keys {
		key := keys[i]
		if v, ok := body[key]; ok {
			encryptString.WriteString(fmt.Sprintf("%s=%v&", key, v))
		}
	}
	encryptString.WriteString(fmt.Sprintf("method=%v", method))

	// 此处用私钥签名
	encryptData, err := RsaSign(c.PrivatePem, encryptString.Bytes())
	if err != nil {
		return "", err
	}

	return base64.StdEncoding.EncodeToString(encryptData), nil
}

func (c *BSNBlockChain) MakeRequest(obj interface{}, action string, signAction, httpMethod string) (*httplib.BeegoHTTPRequest, error) {
	var mapBlockInfo = make(map[string]interface{})
	json.UnmarshalFromString(json.MarshalToString(obj), &mapBlockInfo)
	secret, err := c.Signature(mapBlockInfo, signAction)
	if err != nil {
		return nil, err
	}
	req := httplib.NewBeegoRequest(c.Host+action, httpMethod)
	req.Header("pubKey", c.PublicKey) //url.QueryEscape(string(c.PublicKey))
	req.Header("signature", secret)   //url.QueryEscape(secret)
	req.SetTimeout(time.Second*5, time.Second*5)
	if httpMethod == http.MethodPost || httpMethod == http.MethodPut {
		req.JSONBody(obj)
	}
	if c.EnableDebugLog {
		data, _ := httputil.DumpRequest(req.GetRequest(), true)
		fmt.Println(string(data))
	}
	return req, nil
}

func (c *BSNBlockChain) HandlerResponse(req *httplib.BeegoHTTPRequest, value interface{}) (*Response, error) {
	response := &Response{}
	data, err := req.Bytes()
	if err != nil {
		return nil, err
	}
	rsp, err := req.Response()
	if err != nil {
		return nil, err
	}
	if rsp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("response code:%v status:%v", rsp.StatusCode, rsp.Status)
	}
	err = json.Unmarshal(data, response)
	if err != nil {
		return nil, err
	}
	if c.EnableDebugLog {
		fmt.Println("\nHttp Response-> \n", string(data))
	}
	if response.Code != 0 {
		return nil, fmt.Errorf("upchain code:%v msg:%v", response.Code, response.Message)
	}
	json.Unmarshal(response.Data, value)
	return response, nil
}

func (b *UpToChainRequest) Complete(options *UpToChainOptions) {
	b.InnerDBTable = options.InnerDBTable
	b.InnerPrimaryKey = options.InnerPrimaryKey
	b.InnerPrimaryIssueId = options.InnerPrimaryIssueId
	b.InnerSecondIssueId = options.InnerSecondIssueId
	b.Value = options.Value
	b.Desc = options.Desc
	b.NeedHash = options.NeedHash
}