package implicitcert

import (
	"fmt"
	"io"
	"math/big"

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

var (
	ErrClientGenerateAgreementData = fmt.Errorf("client GenerateAgreementData error")
	errZeroPrivateKey              = fmt.Errorf("private key got zero")
)

type UserKey struct {
	d *sm2.PrivateKey // user's private key
	// w sm2.PublicKey   // declare public key
}

func NewUserKey() *UserKey {
	return &UserKey{}
}

// GeneratePartialKey generate UserKey.d and partial public key U.
// Then user should send ID and U to kgc.
// The Input r should be a random number Reader.
// r could use grand.Reader, or a wrapper of random generator from hard ware.
// If want pass in a b of []byte slice, use grand.NewReaderFromBytes(b) to turn b to a
// Reader:
// b := []byte{...}
// pk,err := u.GeneratePartialKey(grand.NewReaderFromBytes(b))
func (uk *UserKey) GeneratePartialKey(r io.Reader) (*sm2.PublicKey, error) {
	rnd := make([]byte, sm2.ByteSize())
	_, err := r.Read(rnd)
	if err != nil {
		return nil, err
	}
	uk.d, _ = sm2.GenPrivateKey(rnd)
	u := sm2.GenPublicKey(uk.d)
	return u, nil
}

// GeneratePrivateKey generate user's privatekey and the declare public key
// t - KGC generated partial private key for user
func (uk *UserKey) GeneratePrivateKey(kgcGeneratedUserPartialPrivateKey *sm2.PrivateKey) (userPrivateKey *sm2.PrivateKey, err error) {
	d := new(big.Int)
	d.Add(uk.d.D, kgcGeneratedUserPartialPrivateKey.D)
	ret := d.Cmp(sm2.OrderN())
	if ret == 0 {
		return nil, errZeroPrivateKey
	}
	if ret > 0 {
		d.Sub(d, sm2.OrderN())
	}

	return &sm2.PrivateKey{D: d}, nil

}

// VerifyUserKeyPair verify the private key and declare public key are correct
func VerifyUserKeyPair(
	id []byte,
	userPrivateKey *sm2.PrivateKey,
	userDeclarePublicKey *sm2.PublicKey,
	kgcPublicKey *sm2.PublicKey) bool {

	return sm2.VerifyKeyPair(userPrivateKey, RestoreUserPublicKey(id, userDeclarePublicKey, kgcPublicKey))
}

// RestoreUserPublicKey restore user's public key from id, kgc's public key and user's
// declare public key
func RestoreUserPublicKey(
	id []byte,
	userDeclarePublicKey *sm2.PublicKey,
	kgcPublicKey *sm2.PublicKey) *sm2.PublicKey {

	h := sm2.PreComputeWithIdAndPubkey(id, kgcPublicKey)
	lambda := sm3.Sum(userDeclarePublicKey.Bytes(), h)

	x1, y1 := sm2.Curve().ScalarMult(kgcPublicKey.X, kgcPublicKey.Y, lambda[:])
	x1, y1 = sm2.Curve().Add(x1, y1, userDeclarePublicKey.X, userDeclarePublicKey.Y)

	return &sm2.PublicKey{X: x1, Y: y1}
}
