package exchange

import (
	"context"
	"crypto/rsa"
	"fmt"
	"math/rand"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"go.uber.org/zap/zaptest"
	"golang.org/x/sync/errgroup"

	"go.mau.fi/mautrix-telegram/pkg/gotd/crypto"
	"go.mau.fi/mautrix-telegram/pkg/gotd/tdsync"
	"go.mau.fi/mautrix-telegram/pkg/gotd/testutil"
	"go.mau.fi/mautrix-telegram/pkg/gotd/transport"
)

func testExchange(rsaPad bool) func(t *testing.T) {
	return func(t *testing.T) {
		a := require.New(t)
		log := zaptest.NewLogger(t)

		dc := 2
		reader := rand.New(rand.NewSource(1))
		key, err := rsa.GenerateKey(reader, crypto.RSAKeyBits)
		a.NoError(err)
		privateKey := PrivateKey{
			RSA: key,
		}

		i := transport.Intermediate
		client, server := i.Pipe()

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

		g := tdsync.NewCancellableGroup(ctx)
		g.Go(func(ctx context.Context) error {
			_, err := NewExchanger(client, dc).
				WithLogger(log.Named("client")).
				WithRand(reader).
				Client([]PublicKey{privateKey.Public()}).
				Run(ctx)
			return err
		})

		g.Go(func(ctx context.Context) error {
			_, err := NewExchanger(server, dc).
				WithLogger(log.Named("server")).
				WithRand(reader).
				Server(privateKey).
				Run(ctx)
			return err
		})

		a.NoError(g.Wait())
	}
}

func TestExchange(t *testing.T) {
	t.Run("PQInnerData", testExchange(false))
	t.Run("PQInnerDataDC", testExchange(true))
}

func TestExchangeCorpus(t *testing.T) {
	privateKey := PrivateKey{
		RSA: testutil.RSAPrivateKey(),
	}

	for i, seed := range []string{
		"\xef\x00\x04",
	} {
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			dc := 2
			reader := testutil.Rand([]byte(seed))
			log := zaptest.NewLogger(t)

			i := transport.Intermediate
			client, server := i.Pipe()

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

			g, gctx := errgroup.WithContext(ctx)
			g.Go(func() error {
				_, err := NewExchanger(client, dc).
					WithLogger(log.Named("client")).
					WithRand(reader).
					Client([]PublicKey{privateKey.Public()}).
					Run(gctx)
				if err != nil {
					cancel()
				}
				return err
			})
			g.Go(func() error {
				_, err := NewExchanger(server, dc).
					WithLogger(log.Named("server")).
					WithRand(reader).
					Server(privateKey).
					Run(gctx)
				if err != nil {
					cancel()
				}
				return err
			})

			require.NoError(t, g.Wait())
		})
	}
}
