//go:build go1.18
// +build go1.18

package sm2

import (
	"bytes"
	"crypto/rand"
	"fmt"
	"sync"
	"testing"
	"time"

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

var m sync.Mutex

var waittingIcons = string("|/-\\")

var i = 0
var begin time.Time
var last time.Time

func waitting() {
	m.Lock()
	defer m.Unlock()
	if time.Since(last).Milliseconds() > 200 {
		elaspe := time.Since(begin)
		hours := int64(elaspe.Hours())
		elaspe -= time.Duration(hours) * time.Hour
		minutes := int64(elaspe.Minutes())
		elaspe -= time.Duration(minutes) * time.Minute
		seconds := int64(elaspe.Seconds())

		fmt.Printf("\rTest is running for %02dh %02dm %02ds  ", hours, minutes, seconds)
		fmt.Print(string(waittingIcons[i]))
		last = time.Now()
		i++
		if i >= len(waittingIcons) {
			i = 0
		}
	}
}

func TestFuzz(t *testing.T) {
	wg := new(sync.WaitGroup)
	count := 1000
	begin = time.Now()
	last = begin

	// test key generation
	wg.Add(1)
	go func(k int) {
		for i := 0; i < k; i++ {
			k := NewPrivateKey().Random(grand.Reader)
			pk := (&PublicKey{}).Generate(k)
			b, _ := pk.MarshalBinary()
			pk1 := &PublicKey{}
			pk1.UnmarshalBinary(b)
			if !pk1.Equals(pk) || !pk.IsValid() || !pk1.IsValid() {
				t.Log()
				t.Fail()
				break
			}
			if i%100 == 0 {
				waitting()
			}
		}
		wg.Done()
	}(count)

	wg.Add(1)
	go func(k int) {
		for i := 0; i < k; i++ {
			e := grand.GetRandom(ByteSize())
			k := grand.GetRandom(ByteSize())
			d := (&PrivateKey{}).Random(grand.Reader)
			pk := (&PublicKey{}).Generate(d)
			sig, err := Sign(e, k, d)
			if err != nil {
				t.Log()
				t.Fail()
				break
			}

			data, err := sig.MarshalBinary()
			if err != nil {
				t.Log()
				t.Fail()
			}

			sig1 := NewSignature()
			if err := sig1.UnmarshalBinary(data); err != nil {
				t.Log()
				t.Fail()
			}

			ret := Verify(e, pk, sig1)
			if !ret {
				t.Log()
				t.Fail()
				break
			}
			if i%100 == 0 {
				waitting()
			}
		}
		wg.Done()
	}(count)

	wg.Add(1)
	go func(k int) {
		for i := 0; i < k; i++ {
			k := grand.GetRandom(ByteSize())
			d := (&PrivateKey{}).Random(grand.Reader)
			pk := (&PublicKey{}).Generate(d)

			msg := grand.GetRandom(i&0xff + 1)

			cipher, err := Encrypt(pk, msg, k)
			if err != nil {
				t.Logf("pk=%v,msg=%v", pk, msg)
				t.Log(err)
				t.Fail()
				break
			}

			data, _ := cipher.MarshalBinary()
			cipher1 := NewCipher()
			if err := cipher1.UnmarshalBinary(data); err != nil {
				t.Log("unmarshal failed")
				t.Fail()
				break
			}

			//Decrypt
			decryptedMsg, err := Decrypt(d, cipher)
			if err != nil || bytes.Compare(decryptedMsg, msg) != 0 {
				t.Log(err)
				t.Fail()
				break
			}
			if i%100 == 0 {
				waitting()
			}
		}
		wg.Done()
	}(count)

	// TODO generic32 and 64 panic
	// wg.Add(1)
	// go func(k int) {
	// 	for i := 0; i < k; i++ {
	// 		sponsorID := []byte("Sponsor")
	// 		responsorID := []byte("Responsor")
	// 		ska := (&PrivateKey{}).Random(grand.Reader)
	// 		skb := (&PrivateKey{}).Random(grand.Reader)

	// 		// 使用默认id传入nil或GetDefaultID
	// 		s := NewSponsor(sponsorID, ska)
	// 		rs := NewResponsor(responsorID, skb)
	// 		keylen := i%128 + 1
	// 		tempKeyOfSponsor, err := s.GenerateAgreementData(nil)
	// 		if err != nil {
	// 			t.Log()
	// 			t.Fail()
	// 		}
	// 		keyOfResponsor, tempKeyOfResponsor, err := rs.GenerateAgreementDataAndKey(sponsorID, GenPublicKey(ska), tempKeyOfSponsor, keylen, nil)
	// 		if err != nil {
	// 			t.Log()
	// 			t.Fail()
	// 		}
	// 		keyOfSponsor, err := s.GenerateKey(responsorID, GenPublicKey(skb), tempKeyOfResponsor, keylen)
	// 		if err != nil {
	// 			t.Log()
	// 			t.Fail()
	// 		}
	// 		if bytes.Compare(keyOfResponsor, keyOfSponsor) != 0 {
	// 			t.Log()
	// 			t.Fail()
	// 			break
	// 		}

	// 		ska.Clear()
	// 		skb.Clear()
	// 		s.Clear()
	// 		rs.Clear()
	// 		if i%100 == 0 {
	// 			waitting()
	// 		}
	// 	}
	// 	wg.Done()
	// }(count)

	// wait for all routine finish
	wg.Wait()
}

func FuzzSign(f *testing.F) {
	f.Add([]byte{}, []byte{})
	f.Fuzz(func(t *testing.T, k, msg []byte) {
		d := sm3.Sum(k)
		sk := &PrivateKey{}
		sk.SetBytes(d[:])
		e := PreComputeWithIdAndPubkeyAndMessage(GetDefaultID(), msg, &sk.PublicKey)
		r, s, err := SignWithReader(rand.Reader, sk, e)
		if err != nil {
			t.Errorf("%v", err)
		}

		if !VerifyWithRS(&sk.PublicKey, e, r, s) {
			t.Error("verify failed")
		}
	})
}

func FuzzEnc(f *testing.F) {
	f.Add([]byte{}, []byte{}, []byte{})
	f.Fuzz(func(t *testing.T, r1, r2, msg []byte) {
		buf := sm3.Sum(r1)
		d := &PrivateKey{}
		d.SetBytes(buf[:])
		pk := &d.PublicKey
		buf = sm3.Sum(r2)
		k := buf[:]
		cipher, err := Encrypt(pk, msg, k)
		if err != nil {
			t.Error("Encrypt failed")
		}

		//Decrypt
		decryptedMsg, err := Decrypt(d, cipher)
		if err != nil || bytes.Compare(decryptedMsg, msg) != 0 {
			if err != nil {
				t.Error("Dencrypt failed")
			}
		}
	})
}
