package concentration

import (
	"encoding/binary"
	"fmt"
	"hash/crc32"
	"math/big"

	"xdx.jelly/xgcl/gmath"
	"xdx.jelly/xgcl/grand"
	"xdx.jelly/xgcl/sm/sm2"
	"xdx.jelly/xgcl/sm/sm3"
)

var (
	ErrClientGenerateAgreementData = fmt.Errorf("client GenerateAgreementData error")
)

type ClientKeyGenContext struct {
	ks        *sm2.PrivateKey
	ppub      *sm2.PublicKey
	clientPub *sm2.PublicKey
}

func NewClientKeyGenContext() *ClientKeyGenContext {
	return &ClientKeyGenContext{}
}

// GenerateTempKGC output data, and send to server
// in: serverData, server临时公钥U
// out: []byte, ppub_x || ppub_y || W_x || W_y || t || check
func (c *ClientKeyGenContext) GenerateAgreementData(serverData []byte, clientID []byte) ([]byte, *sm2.PublicKey, error) {
	var data []byte
	var err error

	serverTempPubkey := sm2.NewPublicKey()
	if err = serverTempPubkey.UnmarshalBinary(serverData); err != nil {
		return []byte{}, nil, ErrClientGenerateAgreementData
	}

	if c.ks, err = sm2.GenPrivateKey(grand.GetRandom(sm2.ByteSize())); err != nil {
		return []byte{}, nil, ErrClientGenerateAgreementData
	}
	c.ppub = sm2.GenPublicKey(c.ks)
	data = make([]byte, 0, 128+32+4)
	data = append(data, gmath.BigIntToNByte(c.ppub.X, 32)...)
	data = append(data, gmath.BigIntToNByte(c.ppub.Y, 32)...)

	w, err := sm2.GenPrivateKey(grand.GetRandom(sm2.ByteSize()))
	if err != nil {
		return []byte{}, nil, ErrClientGenerateAgreementData
	}
	W := sm2.GenPublicKey(w)
	data = append(data, gmath.BigIntToNByte(W.X, 32)...)
	data = append(data, gmath.BigIntToNByte(W.Y, 32)...)

	// FIXME: W.X.Bytes() may not 32-bytes
	digest := sm3.Sum(W.Bytes(), sm2.PreComputeWithIdAndPubkey(clientID, c.ppub))
	// digest := sm3.Sum(W.X.Bytes(), W.Y.Bytes(), sm2.PreComputeWithIdAndPubkey(clientID, c.ppub))
	t := new(big.Int)
	t.SetBytes(digest[:])
	t.Mul(t, c.ks.D)
	t.Add(t, w.D)
	t.Mod(t, sm2.OrderN())
	data = append(data, gmath.BigIntToNByte(t, sm2.ByteSize())...)

	checkSum := crc32.ChecksumIEEE(data)
	buf := make([]byte, 4)
	binary.BigEndian.PutUint32(buf, checkSum)
	data = append(data, buf...)

	x, y := sm2.Curve256.ScalarBaseMult(t.Bytes())
	x, y = sm2.Curve().Add(x, y, serverTempPubkey.X, serverTempPubkey.Y)
	pk := &sm2.PublicKey{
		X: x,
		Y: y,
	}
	c.clientPub = sm2.NewPublicKey()
	c.clientPub.X.Set(x)
	c.clientPub.Y.Set(y)
	return data, pk, nil
}

type ClientSignContext struct {
	k *sm2.PrivateKey
	r *big.Int
}

func NewClientSignContext() *ClientSignContext {
	return &ClientSignContext{}
}

// GenerateSignData 生成签名中间数据和r
func (c *ClientSignContext) GenerateSignData(serverData []byte, e []byte) (dataToServer []byte, r []byte, err error) {
	c.k = sm2.NewPrivateKey()
	c.k.Random(grand.Reader)
	c.r = new(big.Int)
	c.r.SetBytes(e)
	tempPK := sm2.NewPublicKey()
	tempPK.X.SetBytes(serverData[:32])
	tempPK.Y.SetBytes(serverData[32:])
	x, _ := sm2.Curve256.ScalarMult(tempPK.X, tempPK.Y, c.k.Bytes())
	c.r.Add(c.r, x)
	c.r.Mod(c.r, sm2.OrderN())

	return gmath.BigIntToNByte(c.k.D, 32), gmath.BigIntToNByte(c.r, 32), nil
}
