package benchmark

import (
	"bytes"
	"context"
	"sync"

	"testing"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/protobuf/types/known/anypb"

	base "github.com/CaduceusMetaverseProtocol/metaprotocol/gen/proto/go/base/v1"
	ring "github.com/CaduceusMetaverseProtocol/metaprotocol/gen/proto/go/ring/v1"
)

// go  test -v -run EthTx  -bench=.  -benchtime=3s
// go test   -v -run  BenchmarkQueueAnyTx -bench BenchmarkQueueAnyTx -benchtime=3s  -benchmem
// go test   -v -run  BenchmarkQueueEthTx -bench BenchmarkQueueEthTx -benchtime=3s  -benchmem
// go test   -v -run  BenchmarkQueueStdTx -bench BenchmarkQueueStdTx -benchtime=3s  -benchmem
// go test   -v -run  BenchmarkQueueBytesEth -bench BenchmarkQueueBytesEth -benchtime=3s  -benchmem

var once sync.Once
var onceHash sync.Once

func BenchmarkQueueEthTx(b *testing.B) {

	b.Logf("b.N: %d  \n", b.N)

	// firstDone := make(chan bool, 1)

	// onceFunc := func() {

	// 	go ProduceOriginalTx(firstDone)

	// 	<-firstDone
	// }

	// once.Do(onceFunc)

	//b.StopTimer()

	//onceFunc := func() {
	txsQueue, err := ProduceOriginalTxByCount(500000)

	if err != nil {
		b.Fatal(err)
	}

	originalTxsQueue = txsQueue
	//}

	//once.Do(onceFunc)

	b.ResetTimer()

	//b.StartTimer()

	b.RunParallel(func(pb *testing.PB) {

		for pb.Next() {
			conn, err := grpc.Dial("127.0.0.1:9006", grpc.WithTransportCredentials(insecure.NewCredentials()))
			if err != nil {
				b.Fatal(err)
			}
			defer conn.Close()

			c := ring.NewRingServiceClient(conn)

			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			tx := <-originalTxsQueue

			inner := base.EthTxData{
				AccountNonce: tx.Nonce(),
				Price:        tx.GasPrice().Bytes(),
				GasLimit:     tx.Gas(),
				Payload:      tx.Data(),
			}

			v, r, sigs := tx.RawSignatureValues()

			inner.V = v.Bytes()
			inner.R = r.Bytes()
			inner.S = sigs.Bytes()
			inner.Amount = tx.Value().Bytes()

			addr := base.Address{Address: tx.To().Bytes()}

			inner.Recipient = &addr
			inner.From = FromAddr.Bytes()

			res, err := c.SendTxAsEth(ctx, &base.TransactionEth{Tx: &base.EthTx{Inner: &inner}})

			if err != nil {
				b.Fatal(err)
			}

			_ = res

			if bytes.Compare(tx.Hash().Bytes(), res.TxHash) != 0 {
				b.Fatal(err)
			}

			onceHash.Do(func() {
				b.Logf("response: %x  local: %x \n", res.TxHash, tx.Hash().Bytes())
			})
		}
	})

}

func BenchmarkQueueStdTx(b *testing.B) {

	b.Logf("b.N: %d\n", b.N)

	// firstDone := make(chan bool, 1)

	// onceFunc := func() {

	// 	go ProduceOriginalTx(firstDone)

	// 	<-firstDone
	// }

	// once.Do(onceFunc)

	//onceFunc := func() {
	txsQueue, err := ProduceOriginalTxByCount(500000)

	if err != nil {
		b.Fatal(err)
	}

	originalTxsQueue = txsQueue
	//}
	//once.Do(onceFunc)

	b.ResetTimer()

	b.RunParallel(func(pb *testing.PB) {

		for pb.Next() {

			conn, err := grpc.Dial("127.0.0.1:9006", grpc.WithTransportCredentials(insecure.NewCredentials()))
			if err != nil {
				b.Fatal(err)
			}
			defer conn.Close()

			c := ring.NewRingServiceClient(conn)

			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			tx := <-originalTxsQueue

			inner := base.StdTxData{
				AccountNonce: tx.Nonce(),
				Price:        tx.GasPrice().Bytes(),
				GasLimit:     tx.Gas(),
				Payload:      tx.Data(),
			}

			v, r, sigs := tx.RawSignatureValues()

			inner.V = v.Bytes()
			inner.R = r.Bytes()
			inner.S = sigs.Bytes()
			inner.Amount = tx.Value().Bytes()

			addr := base.Address{Address: tx.To().Bytes()}

			inner.Recipient = &addr
			inner.From = FromAddr.Bytes()

			res, err := c.SendTxAsStd(ctx, &base.TransactionStd{Tx: &base.StdTx{Inner: &inner}})

			if err != nil {
				b.Fatal(err)
			}

			_ = res
			//fmt.Printf("%x \n", res.TxHash)
			if bytes.Compare(tx.Hash().Bytes(), res.TxHash) != 0 {
				b.Fatal(err)
			}

			onceHash.Do(func() {
				b.Logf("response: %x  local: %x \n", res.TxHash, tx.Hash().Bytes())
			})

		}
	})
}

func BenchmarkQueueAnyTx(b *testing.B) {

	b.Logf("b.N: %d  \n", b.N)

	// firstDone := make(chan bool, 1)

	// onceFunc := func() {

	// 	go ProduceOriginalTx(firstDone)

	// 	<-firstDone
	// }

	// once.Do(onceFunc)

	//onceFunc := func() {
	txsQueue, err := ProduceOriginalTxByCount(500000)

	if err != nil {
		b.Fatal(err)
	}

	originalTxsQueue = txsQueue
	//}

	//once.Do(onceFunc)

	b.ResetTimer()

	// The loop body is executed b.N times total across all goroutines.

	b.RunParallel(func(pb *testing.PB) {

		for pb.Next() {

			conn, err := grpc.Dial("127.0.0.1:9006", grpc.WithTransportCredentials(insecure.NewCredentials()))
			if err != nil {
				b.Fatal(err)
			}
			defer conn.Close()

			c := ring.NewRingServiceClient(conn)

			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			tx := <-originalTxsQueue

			inner := base.EthTxData{
				AccountNonce: tx.Nonce(),
				Price:        tx.GasPrice().Bytes(),
				GasLimit:     tx.Gas(),
				Payload:      tx.Data(),
			}

			v, r, sigs := tx.RawSignatureValues()

			inner.V = v.Bytes()
			inner.R = r.Bytes()
			inner.S = sigs.Bytes()
			inner.Amount = tx.Value().Bytes()

			addr := base.Address{Address: tx.To().Bytes()}

			inner.Recipient = &addr
			inner.From = FromAddr.Bytes()

			ethTx := base.EthTx{Inner: &inner}

			ethTxAsAny, err := anypb.New(&ethTx)

			res, err := c.SendTxAsAny(ctx, &base.Transaction{Tx: ethTxAsAny})

			if err != nil {
				b.Fatal(err)
			}

			_ = res

			if bytes.Compare(tx.Hash().Bytes(), res.TxHash) != 0 {
				b.Fatal(err)
			}

			onceHash.Do(func() {
				b.Logf("response: %x  local: %x \n", res.TxHash, tx.Hash().Bytes())
			})

		}
	})
}

func BenchmarkQueueBytesEth(b *testing.B) {

	b.Logf("b.N: %d  \n", b.N)

	// firstDone := make(chan bool, 1)

	// onceFunc := func() {

	// 	go ProduceOriginalTx(firstDone)

	// 	<-firstDone
	// }

	// once.Do(onceFunc)

	//onceFunc := func() {
	txsQueue, err := ProduceOriginalTxByCount(500000)

	if err != nil {
		b.Fatal(err)
	}

	originalTxsQueue = txsQueue
	//}

	//once.Do(onceFunc)

	b.ResetTimer()

	b.RunParallel(func(pb *testing.PB) {

		for pb.Next() {

			conn, err := grpc.Dial("127.0.0.1:9006", grpc.WithTransportCredentials(insecure.NewCredentials()))
			if err != nil {
				b.Fatal(err)
			}
			defer conn.Close()

			c := ring.NewRingServiceClient(conn)

			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			tx := <-originalTxsQueue

			txAsBytes, err := tx.MarshalBinary()

			if err != nil {
				b.Fatal(err)
			}

			res, err := c.SendTxAsBytes(ctx, &base.TransactionBytes{Tx: txAsBytes, From: FromAddr[:]})

			if err != nil {
				b.Fatal(err)
			}

			_ = res

			if bytes.Compare(tx.Hash().Bytes(), res.TxHash) != 0 {
				b.Fatal(err)
			}

			onceHash.Do(func() {
				b.Logf("response: %x  local: %x \n", res.TxHash, tx.Hash().Bytes())
			})

		}
	})
}