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

package curves

import (
	"crypto/elliptic"
	crand "crypto/rand"
	"crypto/sha256"
	"crypto/subtle"
	"fmt"
	"io"
	"math/big"
	"testing"

	"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core"
)

func BenchmarkP256(b *testing.B) {
	// 1000 points

	b.Run("1000 point hash - p256", func(b *testing.B) {
		b.StopTimer()
		points := make([][]byte, 1000)
		for i := range points {
			t := make([]byte, 32)
			_, _ = crand.Read(t)
			points[i] = t
		}
		acc := new(BenchPointP256).Identity()
		b.StartTimer()
		for _, pt := range points {
			acc = acc.Hash(pt)
		}
	})

	b.Run("1000 point hash - ct p256", func(b *testing.B) {
		b.StopTimer()
		points := make([][]byte, 1000)
		for i := range points {
			t := make([]byte, 32)
			_, _ = crand.Read(t)
			points[i] = t
		}
		acc := new(PointP256).Identity()
		b.StartTimer()
		for _, pt := range points {
			acc = acc.Hash(pt)
		}
	})

	b.Run("1000 point add - p256", func(b *testing.B) {
		b.StopTimer()
		points := make([]*BenchPointP256, 1000)
		for i := range points {
			points[i] = points[i].Random(crand.Reader).(*BenchPointP256)
		}
		acc := new(BenchPointP256).Identity()
		b.StartTimer()
		for _, pt := range points {
			acc = acc.Add(pt)
		}
	})
	b.Run("1000 point add - ct p256", func(b *testing.B) {
		b.StopTimer()
		curve := P256()
		points := make([]*PointP256, 1000)
		for i := range points {
			points[i] = curve.NewIdentityPoint().Random(crand.Reader).(*PointP256)
		}
		acc := curve.NewIdentityPoint()
		b.StartTimer()
		for _, pt := range points {
			acc = acc.Add(pt)
		}
	})
	b.Run("1000 point double - p256", func(b *testing.B) {
		b.StopTimer()
		acc := new(BenchPointP256).Generator()
		b.StartTimer()
		for i := 0; i < 1000; i++ {
			acc = acc.Double()
		}
	})
	b.Run("1000 point double - ct p256", func(b *testing.B) {
		b.StopTimer()
		acc := new(PointP256).Generator()
		b.StartTimer()
		for i := 0; i < 1000; i++ {
			acc = acc.Double()
		}
	})
	b.Run("1000 point multiply - p256", func(b *testing.B) {
		b.StopTimer()
		scalars := make([]*BenchScalarP256, 1000)
		for i := range scalars {
			s := new(BenchScalarP256).Random(crand.Reader)
			scalars[i] = s.(*BenchScalarP256)
		}
		acc := new(BenchPointP256).Generator().Mul(new(BenchScalarP256).New(2))
		b.StartTimer()
		for _, sc := range scalars {
			acc = acc.Mul(sc)
		}
	})
	b.Run("1000 point multiply - ct p256", func(b *testing.B) {
		b.StopTimer()
		scalars := make([]*ScalarP256, 1000)
		for i := range scalars {
			s := new(ScalarP256).Random(crand.Reader)
			scalars[i] = s.(*ScalarP256)
		}
		acc := new(PointP256).Generator()
		b.StartTimer()
		for _, sc := range scalars {
			acc = acc.Mul(sc)
		}
	})
	b.Run("1000 scalar invert - p256", func(b *testing.B) {
		b.StopTimer()
		scalars := make([]*BenchScalarP256, 1000)
		for i := range scalars {
			s := new(BenchScalarP256).Random(crand.Reader)
			scalars[i] = s.(*BenchScalarP256)
		}
		b.StartTimer()
		for _, sc := range scalars {
			_, _ = sc.Invert()
		}
	})
	b.Run("1000 scalar invert - ct p256", func(b *testing.B) {
		b.StopTimer()
		scalars := make([]*ScalarP256, 1000)
		for i := range scalars {
			s := new(ScalarP256).Random(crand.Reader)
			scalars[i] = s.(*ScalarP256)
		}
		b.StartTimer()
		for _, sc := range scalars {
			_, _ = sc.Invert()
		}
	})
	b.Run("1000 scalar sqrt - p256", func(b *testing.B) {
		b.StopTimer()
		scalars := make([]*BenchScalarP256, 1000)
		for i := range scalars {
			s := new(BenchScalarP256).Random(crand.Reader)
			scalars[i] = s.(*BenchScalarP256)
		}
		b.StartTimer()
		for _, sc := range scalars {
			_, _ = sc.Sqrt()
		}
	})
	b.Run("1000 scalar sqrt - ct p256", func(b *testing.B) {
		b.StopTimer()
		scalars := make([]*ScalarP256, 1000)
		for i := range scalars {
			s := new(ScalarP256).Random(crand.Reader)
			scalars[i] = s.(*ScalarP256)
		}
		b.StartTimer()
		for _, sc := range scalars {
			_, _ = sc.Sqrt()
		}
	})
}

type BenchScalarP256 struct {
	value *big.Int
}

type BenchPointP256 struct {
	x, y *big.Int
}

func (s *BenchScalarP256) Random(reader io.Reader) Scalar {
	if reader == nil {
		return nil
	}
	var seed [64]byte
	_, _ = reader.Read(seed[:])
	return s.Hash(seed[:])
}

func (s *BenchScalarP256) Hash(bytes []byte) Scalar {
	xmd, err := expandMsgXmd(sha256.New(), bytes, []byte("P256_XMD:SHA-256_SSWU_RO_"), 48)
	if err != nil {
		return nil
	}
	v := new(big.Int).SetBytes(xmd)
	return &BenchScalarP256{
		value: v.Mod(v, elliptic.P256().Params().N),
	}
}

func (s *BenchScalarP256) Zero() Scalar {
	return &BenchScalarP256{
		value: big.NewInt(0),
	}
}

func (s *BenchScalarP256) One() Scalar {
	return &BenchScalarP256{
		value: big.NewInt(1),
	}
}

func (s *BenchScalarP256) IsZero() bool {
	return subtle.ConstantTimeCompare(s.value.Bytes(), []byte{}) == 1
}

func (s *BenchScalarP256) IsOne() bool {
	return subtle.ConstantTimeCompare(s.value.Bytes(), []byte{1}) == 1
}

func (s *BenchScalarP256) IsOdd() bool {
	return s.value.Bit(0) == 1
}

func (s *BenchScalarP256) IsEven() bool {
	return s.value.Bit(0) == 0
}

func (s *BenchScalarP256) New(value int) Scalar {
	v := big.NewInt(int64(value))
	if value < 0 {
		v.Mod(v, elliptic.P256().Params().N)
	}
	return &BenchScalarP256{
		value: v,
	}
}

func (s *BenchScalarP256) Cmp(rhs Scalar) int {
	r, ok := rhs.(*BenchScalarP256)
	if ok {
		return s.value.Cmp(r.value)
	} else {
		return -2
	}
}

func (s *BenchScalarP256) Square() Scalar {
	return &BenchScalarP256{
		value: new(big.Int).Exp(s.value, big.NewInt(2), elliptic.P256().Params().N),
	}
}

func (s *BenchScalarP256) Double() Scalar {
	v := new(big.Int).Add(s.value, s.value)
	return &BenchScalarP256{
		value: v.Mod(v, elliptic.P256().Params().N),
	}
}

func (s *BenchScalarP256) Invert() (Scalar, error) {
	return &BenchScalarP256{
		value: new(big.Int).ModInverse(s.value, elliptic.P256().Params().N),
	}, nil
}

func (s *BenchScalarP256) Sqrt() (Scalar, error) {
	return &BenchScalarP256{
		value: new(big.Int).ModSqrt(s.value, elliptic.P256().Params().N),
	}, nil
}

func (s *BenchScalarP256) Cube() Scalar {
	return &BenchScalarP256{
		value: new(big.Int).Exp(s.value, big.NewInt(3), elliptic.P256().Params().N),
	}
}

func (s *BenchScalarP256) Add(rhs Scalar) Scalar {
	r, ok := rhs.(*BenchScalarP256)
	if ok {
		v := new(big.Int).Add(s.value, r.value)
		return &BenchScalarP256{
			value: v.Mod(v, elliptic.P256().Params().N),
		}
	} else {
		return nil
	}
}

func (s *BenchScalarP256) Sub(rhs Scalar) Scalar {
	r, ok := rhs.(*BenchScalarP256)
	if ok {
		v := new(big.Int).Sub(s.value, r.value)
		return &BenchScalarP256{
			value: v.Mod(v, elliptic.P256().Params().N),
		}
	} else {
		return nil
	}
}

func (s *BenchScalarP256) Mul(rhs Scalar) Scalar {
	r, ok := rhs.(*BenchScalarP256)
	if ok {
		v := new(big.Int).Mul(s.value, r.value)
		return &BenchScalarP256{
			value: v.Mod(v, elliptic.P256().Params().N),
		}
	} else {
		return nil
	}
}

func (s *BenchScalarP256) MulAdd(y, z Scalar) Scalar {
	return s.Mul(y).Add(z)
}

func (s *BenchScalarP256) Div(rhs Scalar) Scalar {
	n := elliptic.P256().Params().N
	r, ok := rhs.(*BenchScalarP256)
	if ok {
		v := new(big.Int).ModInverse(r.value, n)
		v.Mul(v, s.value)
		return &BenchScalarP256{
			value: v.Mod(v, n),
		}
	} else {
		return nil
	}
}

func (s *BenchScalarP256) Neg() Scalar {
	z := new(big.Int).Neg(s.value)
	return &BenchScalarP256{
		value: z.Mod(z, elliptic.P256().Params().N),
	}
}

func (s *BenchScalarP256) SetBigInt(v *big.Int) (Scalar, error) {
	if v == nil {
		return nil, fmt.Errorf("invalid value")
	}
	t := new(big.Int).Mod(v, elliptic.P256().Params().N)
	if t.Cmp(v) != 0 {
		return nil, fmt.Errorf("invalid value")
	}
	return &BenchScalarP256{
		value: t,
	}, nil
}

func (s *BenchScalarP256) BigInt() *big.Int {
	return new(big.Int).Set(s.value)
}

func (s *BenchScalarP256) Bytes() []byte {
	var out [32]byte
	return s.value.FillBytes(out[:])
}

func (s *BenchScalarP256) SetBytes(bytes []byte) (Scalar, error) {
	value := new(big.Int).SetBytes(bytes)
	t := new(big.Int).Mod(value, elliptic.P256().Params().N)
	if t.Cmp(value) != 0 {
		return nil, fmt.Errorf("invalid byte sequence")
	}
	return &BenchScalarP256{
		value: t,
	}, nil
}

func (s *BenchScalarP256) SetBytesWide(bytes []byte) (Scalar, error) {
	if len(bytes) < 32 || len(bytes) > 128 {
		return nil, fmt.Errorf("invalid byte sequence")
	}
	value := new(big.Int).SetBytes(bytes)
	value.Mod(value, elliptic.P256().Params().N)
	return &BenchScalarP256{
		value,
	}, nil
}

func (s *BenchScalarP256) Point() Point {
	return new(BenchPointP256).Identity()
}

func (s *BenchScalarP256) Clone() Scalar {
	return &BenchScalarP256{
		value: new(big.Int).Set(s.value),
	}
}

func (s *BenchScalarP256) MarshalBinary() ([]byte, error) {
	return scalarMarshalBinary(s)
}

func (s *BenchScalarP256) UnmarshalBinary(input []byte) error {
	sc, err := scalarUnmarshalBinary(input)
	if err != nil {
		return err
	}
	ss, ok := sc.(*BenchScalarP256)
	if !ok {
		return fmt.Errorf("invalid scalar")
	}
	s.value = ss.value
	return nil
}

func (s *BenchScalarP256) MarshalText() ([]byte, error) {
	return scalarMarshalText(s)
}

func (s *BenchScalarP256) UnmarshalText(input []byte) error {
	sc, err := scalarUnmarshalText(input)
	if err != nil {
		return err
	}
	ss, ok := sc.(*BenchScalarP256)
	if !ok {
		return fmt.Errorf("invalid scalar")
	}
	s.value = ss.value
	return nil
}

func (s *BenchScalarP256) MarshalJSON() ([]byte, error) {
	return scalarMarshalJson(s)
}

func (s *BenchScalarP256) UnmarshalJSON(input []byte) error {
	sc, err := scalarUnmarshalJson(input)
	if err != nil {
		return err
	}
	S, ok := sc.(*BenchScalarP256)
	if !ok {
		return fmt.Errorf("invalid type")
	}
	s.value = S.value
	return nil
}

func (p *BenchPointP256) Random(reader io.Reader) Point {
	var seed [64]byte
	_, _ = reader.Read(seed[:])
	return p.Hash(seed[:])
}

func (p *BenchPointP256) Hash(bytes []byte) Point {
	curve := elliptic.P256().Params()

	var domain = []byte("P256_XMD:SHA-256_SSWU_RO_")
	uniformBytes, _ := expandMsgXmd(sha256.New(), bytes, domain, 96)

	u0 := new(big.Int).SetBytes(uniformBytes[:48])
	u1 := new(big.Int).SetBytes(uniformBytes[48:])

	u0.Mod(u0, curve.P)
	u1.Mod(u1, curve.P)

	ssParams := p256SswuParams()
	q0x, q0y := osswu3mod4(u0, ssParams)
	q1x, q1y := osswu3mod4(u1, ssParams)

	// Since P-256 does not require the isogeny map just add the points
	x, y := curve.Add(q0x, q0y, q1x, q1y)

	return &BenchPointP256{
		x, y,
	}
}

func (p *BenchPointP256) Identity() Point {
	return &BenchPointP256{
		x: big.NewInt(0), y: big.NewInt(0),
	}
}

func (p *BenchPointP256) Generator() Point {
	curve := elliptic.P256().Params()
	return &BenchPointP256{
		x: new(big.Int).Set(curve.Gx),
		y: new(big.Int).Set(curve.Gy),
	}
}

func (p *BenchPointP256) IsIdentity() bool {
	x := core.ConstantTimeEqByte(p.x, core.Zero)
	y := core.ConstantTimeEqByte(p.y, core.Zero)
	return (x & y) == 1
}

func (p *BenchPointP256) IsNegative() bool {
	return p.y.Bit(0) == 1
}

func (p *BenchPointP256) IsOnCurve() bool {
	return elliptic.P256().IsOnCurve(p.x, p.y)
}

func (p *BenchPointP256) Double() Point {
	curve := elliptic.P256()
	x, y := curve.Double(p.x, p.y)
	return &BenchPointP256{x, y}
}

func (p *BenchPointP256) Scalar() Scalar {
	return new(BenchScalarP256).Zero()
}

func (p *BenchPointP256) Neg() Point {
	y := new(big.Int).Sub(elliptic.P256().Params().P, p.y)
	y.Mod(y, elliptic.P256().Params().P)
	return &BenchPointP256{x: p.x, y: y}
}

func (p *BenchPointP256) Add(rhs Point) Point {
	if rhs == nil {
		return nil
	}
	r, ok := rhs.(*BenchPointP256)
	if ok {
		x, y := elliptic.P256().Add(p.x, p.y, r.x, r.y)
		return &BenchPointP256{x, y}
	} else {
		return nil
	}
}

func (p *BenchPointP256) Sub(rhs Point) Point {
	if rhs == nil {
		return nil
	}
	r, ok := rhs.Neg().(*BenchPointP256)
	if ok {
		x, y := elliptic.P256().Add(p.x, p.y, r.x, r.y)
		return &BenchPointP256{x, y}
	} else {
		return nil
	}
}

func (p *BenchPointP256) Mul(rhs Scalar) Point {
	if rhs == nil {
		return nil
	}
	r, ok := rhs.(*BenchScalarP256)
	if ok {
		x, y := elliptic.P256().ScalarMult(p.x, p.y, r.value.Bytes())
		return &BenchPointP256{x, y}
	} else {
		return nil
	}
}

func (p *BenchPointP256) Equal(rhs Point) bool {
	r, ok := rhs.(*BenchPointP256)
	if ok {
		x := core.ConstantTimeEqByte(p.x, r.x)
		y := core.ConstantTimeEqByte(p.y, r.y)
		return (x & y) == 1
	} else {
		return false
	}
}

func (p *BenchPointP256) Set(x, y *big.Int) (Point, error) {
	// check is identity or on curve
	xx := subtle.ConstantTimeCompare(x.Bytes(), []byte{})
	yy := subtle.ConstantTimeCompare(y.Bytes(), []byte{})
	// Checks are constant time
	onCurve := elliptic.P256().IsOnCurve(x, y)
	if !onCurve && (xx&yy) != 1 {
		return nil, fmt.Errorf("invalid coordinates")
	}
	x = new(big.Int).Set(x)
	y = new(big.Int).Set(y)
	return &BenchPointP256{x, y}, nil
}

func (p *BenchPointP256) ToAffineCompressed() []byte {
	var x [33]byte
	x[0] = byte(2)
	x[0] |= byte(p.y.Bit(0))
	p.x.FillBytes(x[1:])
	return x[:]
}

func (p *BenchPointP256) ToAffineUncompressed() []byte {
	var out [65]byte
	out[0] = byte(4)
	p.x.FillBytes(out[1:33])
	p.y.FillBytes(out[33:])
	return out[:]
}

func (p *BenchPointP256) FromAffineCompressed(bytes []byte) (Point, error) {
	if len(bytes) != 33 {
		return nil, fmt.Errorf("invalid byte sequence")
	}
	sign := int(bytes[0])
	if sign != 2 && sign != 3 {
		return nil, fmt.Errorf("invalid sign byte")
	}
	sign &= 0x1

	x := new(big.Int).SetBytes(bytes[1:])
	rhs := rhsP256(x, elliptic.P256().Params())
	// test that rhs is quadratic residue
	// if not, then this Point is at infinity
	y := new(big.Int).ModSqrt(rhs, elliptic.P256().Params().P)
	if y != nil {
		// fix the sign
		if int(y.Bit(0)) != sign {
			y.Neg(y)
			y.Mod(y, elliptic.P256().Params().P)
		}
	} else {
		x = new(big.Int)
		y = new(big.Int)
	}
	return &BenchPointP256{
		x, y,
	}, nil
}

func (p *BenchPointP256) FromAffineUncompressed(bytes []byte) (Point, error) {
	if len(bytes) != 65 {
		return nil, fmt.Errorf("invalid byte sequence")
	}
	if bytes[0] != 4 {
		return nil, fmt.Errorf("invalid sign byte")
	}
	x := new(big.Int).SetBytes(bytes[1:33])
	y := new(big.Int).SetBytes(bytes[33:])
	return &BenchPointP256{x, y}, nil
}

func (p *BenchPointP256) CurveName() string {
	return elliptic.P256().Params().Name
}

func (p *BenchPointP256) SumOfProducts(points []Point, scalars []Scalar) Point {
	nScalars := make([]*big.Int, len(scalars))
	for i, sc := range scalars {
		s, ok := sc.(*BenchScalarP256)
		if !ok {
			return nil
		}
		nScalars[i] = s.value
	}
	return sumOfProductsPippenger(points, nScalars)
}

func (p *BenchPointP256) X() *big.Int {
	return new(big.Int).Set(p.x)
}

func (p *BenchPointP256) Y() *big.Int {
	return new(big.Int).Set(p.y)
}

func (p *BenchPointP256) Params() *elliptic.CurveParams {
	return elliptic.P256().Params()
}

func (p *BenchPointP256) MarshalBinary() ([]byte, error) {
	return pointMarshalBinary(p)
}

func (p *BenchPointP256) UnmarshalBinary(input []byte) error {
	pt, err := pointUnmarshalBinary(input)
	if err != nil {
		return err
	}
	ppt, ok := pt.(*BenchPointP256)
	if !ok {
		return fmt.Errorf("invalid point")
	}
	p.x = ppt.x
	p.y = ppt.y
	return nil
}

func (p *BenchPointP256) MarshalText() ([]byte, error) {
	return pointMarshalText(p)
}

func (p *BenchPointP256) UnmarshalText(input []byte) error {
	pt, err := pointUnmarshalText(input)
	if err != nil {
		return err
	}
	ppt, ok := pt.(*BenchPointP256)
	if !ok {
		return fmt.Errorf("invalid point")
	}
	p.x = ppt.x
	p.y = ppt.y
	return nil
}

func (p *BenchPointP256) MarshalJSON() ([]byte, error) {
	return pointMarshalJson(p)
}

func (p *BenchPointP256) UnmarshalJSON(input []byte) error {
	pt, err := pointUnmarshalJson(input)
	if err != nil {
		return err
	}
	P, ok := pt.(*BenchPointP256)
	if !ok {
		return fmt.Errorf("invalid type")
	}
	p.x = P.x
	p.y = P.y
	return nil
}

// From https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.2
func p256SswuParams() *sswuParams {
	params := elliptic.P256().Params()

	// c1 = (q - 3) / 4
	c1 := new(big.Int).Set(params.P)
	c1.Sub(c1, big.NewInt(3))
	c1.Rsh(c1, 2)

	a := big.NewInt(-3)
	a.Mod(a, params.P)
	b := new(big.Int).Set(params.B)
	z := big.NewInt(-10)
	z.Mod(z, params.P)
	// sqrt(-Z^3)
	zTmp := new(big.Int).Exp(z, big.NewInt(3), nil)
	zTmp = zTmp.Neg(zTmp)
	zTmp.Mod(zTmp, params.P)
	c2 := new(big.Int).ModSqrt(zTmp, params.P)

	return &sswuParams{
		params, c1, c2, a, b, z,
	}
}

//rhs of the curve equation
func rhsP256(x *big.Int, params *elliptic.CurveParams) *big.Int {
	f := NewField(params.P)
	r := f.NewElement(x)
	r2 := r.Mul(r)

	// x^3-3x+B
	a := r.Mul(f.NewElement(big.NewInt(3)))
	r = r2.Mul(r)
	return r.Add(a.Neg()).Add(f.NewElement(params.B)).Value
}
