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

package curves

import (
	crand "crypto/rand"
	"testing"

	"github.com/stretchr/testify/require"

	"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves/native/pasta/fp"
	"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves/native/pasta/fq"
)

func TestPointPallasAddDoubleMul(t *testing.T) {
	g := new(Ep).Generator()
	id := new(Ep).Identity()
	require.Equal(t, g.Add(g, id), g)

	g2 := new(Ep).Add(g, g)
	require.True(t, new(Ep).Double(g).Equal(g2))
	require.Equal(t, new(Ep).Double(g), new(Ep).Add(g, g))
	g3 := new(Ep).Add(g, g2)
	require.True(t, g3.Equal(new(Ep).Mul(g, new(fq.Fq).SetUint64(3))))

	g4 := new(Ep).Add(g3, g)
	require.True(t, g4.Equal(new(Ep).Double(g2)))
	require.True(t, g4.Equal(new(Ep).Mul(g, new(fq.Fq).SetUint64(4))))
}

func TestPointPallasHash(t *testing.T) {
	h0 := new(Ep).Hash(nil)
	require.True(t, h0.IsOnCurve())
	h1 := new(Ep).Hash([]byte{})
	require.True(t, h1.IsOnCurve())
	require.True(t, h0.Equal(h1))
	h2 := new(Ep).Hash([]byte{1})
	require.True(t, h2.IsOnCurve())
}

func TestPointPallasNeg(t *testing.T) {
	g := new(Ep).Generator()
	g.Neg(g)
	require.True(t, g.Neg(g).Equal(new(Ep).Generator()))
	id := new(Ep).Identity()
	require.True(t, new(Ep).Neg(id).Equal(id))
}

func TestPointPallasRandom(t *testing.T) {
	a := new(Ep).Random(testRng())
	require.NotNil(t, a.x)
	require.NotNil(t, a.y)
	require.NotNil(t, a.z)
	require.True(t, a.IsOnCurve())
	e := &Ep{
		x: &fp.Fp{
			0x7263083d01d4859c,
			0x65a03323b5a3d204,
			0xe71d73222b136668,
			0x1d1b1bcf1256b539,
		},
		y: &fp.Fp{
			0x8cc2516ffe23e1bb,
			0x5418f941eeaca812,
			0x16c9af658a846f29,
			0x11c572091c418668,
		},
		z: &fp.Fp{
			0xa879589adb77a88e,
			0x5444a531a19f2406,
			0x637ff77c51dda524,
			0x0369e90d219ce821,
		},
	}
	require.True(t, a.Equal(e))
}

func TestPointPallasSerialize(t *testing.T) {
	ss := new(ScalarPallas).Random(testRng()).(*ScalarPallas)
	g := new(Ep).Generator()

	ppt := new(Ep).Mul(g, ss.value)
	require.Equal(t, ppt.ToAffineCompressed(), []byte{0x1c, 0x6d, 0x47, 0x1f, 0x4a, 0x81, 0xcd, 0x8, 0x4e, 0xb3, 0x17, 0x9a, 0xcd, 0x17, 0xe2, 0x9a, 0x24, 0x69, 0xb, 0x4e, 0x69, 0x5f, 0x35, 0x1a, 0x92, 0x12, 0x95, 0xc9, 0xe6, 0xd3, 0x7a, 0x0})
	require.Equal(t, ppt.ToAffineUncompressed(), []byte{0x1c, 0x6d, 0x47, 0x1f, 0x4a, 0x81, 0xcd, 0x8, 0x4e, 0xb3, 0x17, 0x9a, 0xcd, 0x17, 0xe2, 0x9a, 0x24, 0x69, 0xb, 0x4e, 0x69, 0x5f, 0x35, 0x1a, 0x92, 0x12, 0x95, 0xc9, 0xe6, 0xd3, 0x7a, 0x0, 0x80, 0x5c, 0xa1, 0x56, 0x6d, 0x1b, 0x87, 0x5f, 0xb0, 0x2e, 0xae, 0x85, 0x4e, 0x86, 0xa9, 0xcd, 0xde, 0x37, 0x6a, 0xc8, 0x4a, 0x80, 0xf6, 0x43, 0xaa, 0xe6, 0x2c, 0x2d, 0x15, 0xdb, 0xda, 0x29})
	retP, err := new(Ep).FromAffineCompressed(ppt.ToAffineCompressed())
	require.NoError(t, err)
	require.True(t, ppt.Equal(retP))
	retP, err = new(Ep).FromAffineUncompressed(ppt.ToAffineUncompressed())
	require.NoError(t, err)
	require.True(t, ppt.Equal(retP))

	// smoke test
	for i := 0; i < 25; i++ {
		s := new(ScalarPallas).Random(crand.Reader).(*ScalarPallas)
		pt := new(Ep).Mul(g, s.value)
		cmprs := pt.ToAffineCompressed()
		require.Equal(t, len(cmprs), 32)
		retC, err := new(Ep).FromAffineCompressed(cmprs)
		require.NoError(t, err)
		require.True(t, pt.Equal(retC))

		un := pt.ToAffineUncompressed()
		require.Equal(t, len(un), 64)
		retU, err := new(Ep).FromAffineUncompressed(un)
		require.NoError(t, err)
		require.True(t, pt.Equal(retU))
	}
}

func TestPointPallasCMove(t *testing.T) {
	a := new(Ep).Random(crand.Reader)
	b := new(Ep).Random(crand.Reader)
	require.True(t, new(Ep).CMove(a, b, 1).Equal(b))
	require.True(t, new(Ep).CMove(a, b, 0).Equal(a))
}

func TestPointPallasSumOfProducts(t *testing.T) {
	lhs := new(Ep).Generator()
	lhs.Mul(lhs, new(fq.Fq).SetUint64(50))
	points := make([]*Ep, 5)
	for i := range points {
		points[i] = new(Ep).Generator()
	}
	scalars := []Scalar{
		new(ScalarPallas).New(8),
		new(ScalarPallas).New(9),
		new(ScalarPallas).New(10),
		new(ScalarPallas).New(11),
		new(ScalarPallas).New(12),
	}
	rhs := lhs.SumOfProducts(points, scalars)
	require.NotNil(t, rhs)
	require.True(t, lhs.Equal(rhs))
}
