//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

package ted25519

import (
	"encoding/binary"
	"fmt"

	"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves"
	v1 "source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/sharing/v1"
)

// PublicKeyFromBytes converts byte array into PublicKey byte array
func PublicKeyFromBytes(bytes []byte) ([]byte, error) {
	if l := len(bytes); l != PublicKeySize {
		return nil, fmt.Errorf("invalid public key size: %d", l)
	}

	return bytes, nil
}

// KeyShare represents a share of a generated key.
type KeyShare struct {
	*v1.ShamirShare
}

// NewKeyShare is a KeyShare constructor.
func NewKeyShare(identifier byte, secret []byte) *KeyShare {
	field := curves.NewField(curves.Ed25519Order())
	return &KeyShare{v1.NewShamirShare(uint32(identifier), secret, field)}
}

// Commitments is a collection of public keys with each coefficient of a polynomial as the secret keys.
type Commitments []curves.Point

// CommitmentsToBytes converts commitments to bytes
func (commitments Commitments) CommitmentsToBytes() [][]byte {
	bytes := make([][]byte, len(commitments))

	for i, c := range commitments {
		bytes[i] = c.ToAffineCompressed()
	}

	return bytes
}

// CommitmentsFromBytes converts bytes to commitments
func CommitmentsFromBytes(bytes [][]byte) (Commitments, error) {
	comms := make([]curves.Point, len(bytes))
	for i, pubKeyBytes := range bytes {
		pubKey, err := PublicKeyFromBytes(pubKeyBytes)
		if err != nil {
			return nil, err
		}
		comms[i], err = new(curves.PointEd25519).FromAffineCompressed(pubKey)
		if err != nil {
			return nil, err
		}
	}
	return comms, nil
}

// KeyShareFromBytes converts byte array into KeyShare type
func KeyShareFromBytes(bytes []byte) *KeyShare {
	field := curves.NewField(curves.Ed25519Order())
	element := field.ElementFromBytes(bytes[4:])

	// We set first 4 bytes as identifier
	identifier := binary.BigEndian.Uint32(bytes[:4])
	return &KeyShare{&v1.ShamirShare{Identifier: identifier, Value: element}}
}

// ShareConfiguration sets threshold and limit for the protocol
type ShareConfiguration struct {
	T int // threshold
	N int // total shares
}

// generateSharableKey generates a random key and returns the public key and private key in
// big-endian encoding. It returns an error if it cannot acquire sufficient randomness.
func generateSharableKey() (PublicKey, []byte, error) {
	pub, priv, err := GenerateKey(nil)
	if err != nil {
		return nil, nil, err
	}

	// Internally the PrivateKey type is represented as the seed || public key, but we want to pull
	// out seed to share which is the actual private key.
	seed := priv.Seed()

	// We must apply the key expansion to the seed before splitting the key.
	// Ed25519 signing by default will apply this during signature generation, but since it involves
	// a hash function, it breaks the relationship between shares and breaks aggregating signatures.
	// Our signature generation does not apply this mutation at signing time.
	//
	// As per anything that comes from the ed25519 library this value should be treated as
	// little-endian so we reverse it before using it.
	expandedSeed := reverseBytes(ExpandSeed(seed))

	// Lastly we must reduce this value into the size of the field so we can share it. This diverges
	// from how the standard implementation treats this because their scalar multiplication accepts
	// values up to the curve order but we must constrain it to be able to split it and aggregate.
	//
	// If you read the documentation for the ReducedElementFromBytes function we call below, it
	// includes a big warning about how it will return non-uniform outputs depending on the input.
	// This is true, but not a concern for keygen specifically because the value we are providing it
	// has been generated as the ed25519 spec requires, which has a slight bias by definition of how
	// the ExpandSeed operation works.
	field := &curves.Field{Int: curves.Ed25519Order()}
	expandedSeedReduced := field.ReducedElementFromBytes(expandedSeed)

	return pub, expandedSeedReduced.Bytes(), nil
}

// GenerateSharedKey generates a random key, splits it, and returns the public key, shares, and VSS commitments.
func GenerateSharedKey(config *ShareConfiguration) (PublicKey, []*KeyShare, Commitments, error) {
	pub, priv, err := generateSharableKey()
	//pub, priv, err := ed25519.GenerateKey(nil)
	if err != nil {
		return nil, nil, nil, err
	}

	keyShares, commitments, err := splitPrivateKey(config, priv)
	if err != nil {
		return nil, nil, nil, err
	}

	return pub, keyShares, commitments, nil
}

// splitPrivateKey splits the secret into a set of secret shares and creates a set of commitments of them.
func splitPrivateKey(config *ShareConfiguration, priv []byte) ([]*KeyShare, Commitments, error) {
	commitments, shares, err := split(priv, config)
	if err != nil {
		return nil, nil, err
	}

	keyShares := make([]*KeyShare, len(shares))
	for i, s := range shares {
		keyShares[i] = &KeyShare{s}
	}
	return keyShares, commitments, nil
}

// split contains core operations to split the secret and generate commitments.
func split(secret []byte, config *ShareConfiguration) ([]curves.Point, []*v1.ShamirShare, error) {
	field := curves.NewField(curves.Ed25519Order())
	shamir, err := v1.NewShamir(config.T, config.N, field)
	if err != nil {
		return nil, nil, fmt.Errorf("Error in NewShamir")
	}
	shares, poly, err := shamir.GetSharesAndPolynomial(secret)
	if err != nil {
		return nil, nil, fmt.Errorf("Error in GetSharesAndPolynomial")
	}

	// Generate the verifiable commitments to the polynomial for the shares
	verifiers := make([]curves.Point, len(poly.Coefficients))
	// curve := sharing.Ed25519()
	for i, c := range poly.Coefficients {
		// We have to reverse each coefficient, which is different than the method sharing.Split
		reverseC := reverseBytes(c.Bytes())
		var reverseInput [32]byte
		copy(reverseInput[:], reverseC)
		cScalar, err := new(curves.ScalarEd25519).SetBytesCanonical(reverseInput[:])
		if err != nil {
			return nil, nil, fmt.Errorf("Error in SetBytesCanonical reverseC")
		}
		v := curves.ED25519().Point.Generator().Mul(cScalar)
		verifiers[i] = v
	}
	return verifiers, shares, nil
}

// Reconstruct recovers the secret from a set of secret shares.
func Reconstruct(keyShares []*KeyShare, config *ShareConfiguration) ([]byte, error) {
	curve := v1.Ed25519()
	field := curves.NewField(curve.Params().N)
	shamir, err := v1.NewShamir(config.T, config.N, field)
	if err != nil {
		return nil, err
	}

	shares := make([]*v1.ShamirShare, len(keyShares))
	for i, s := range keyShares {
		shares[i] = s.ShamirShare
	}
	return shamir.Combine(shares...)
}

// VerifyVSS validates that a Share represents a solution to a Shamir polynomial
// in which len(commitments) + 1 solutions are required to construct the private
// key for the public key at commitments[0].
func (share *KeyShare) VerifyVSS(commitments Commitments, config *ShareConfiguration) (bool, error) {
	if len(commitments) < config.T {
		return false, fmt.Errorf("not enough verifiers to check")
	}
	field := curves.NewField(curves.Ed25519Order())
	xBytes := make([]byte, 4)
	binary.BigEndian.PutUint32(xBytes, share.Identifier)
	x := field.ElementFromBytes(xBytes)
	i := share.Value.Modulus.One()

	// c_0
	rhs := commitments[0]

	// Compute the sum of products
	// c_0 * c_1^i * c_2^{i^2} *c_3^{i^3}
	for j := 1; j < len(commitments); j++ {
		// i *= x
		i = i.Mul(x)

		var iBytes [32]byte
		copy(iBytes[:], i.Bytes()[:])
		iScalar, err := new(curves.ScalarEd25519).SetBytesCanonical(iBytes[:])
		if err != nil {
			return false, fmt.Errorf("Error in SetBytesCanonical iBytes")
		}
		c := commitments[j].Mul(iScalar)

		// ...* c_j^{i^j}
		rhs = rhs.Add(c)
	}

	vValue := reverseBytes(share.Value.Bytes())
	var vInput [32]byte
	copy(vInput[:], vValue)
	vScalar, err := new(curves.ScalarEd25519).SetBytes(vInput[:])
	if err != nil {
		return false, err
	}
	lhs := curves.ED25519().ScalarBaseMult(vScalar)

	// Check if lhs == rhs
	return lhs.Equal(rhs), nil
}
