package self_test

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io"
	mrand "math/rand"
	"sync"
	"testing"
	"time"

	"github.com/quic-go/quic-go"
	"github.com/quic-go/quic-go/internal/protocol"
	"github.com/quic-go/quic-go/internal/utils"
	"github.com/quic-go/quic-go/logging"
	"github.com/quic-go/quic-go/metrics"
	"github.com/quic-go/quic-go/qlog"

	"github.com/stretchr/testify/require"
)

func TestTracerHandshake(t *testing.T) {
	addTracers := func(pers protocol.Perspective, conf *quic.Config) *quic.Config {
		enableQlog := mrand.Int()%2 != 0
		enableMetrics := mrand.Int()%2 != 0
		enableCustomTracer := mrand.Int()%2 != 0

		t.Logf("%s using qlog: %t, metrics: %t, custom: %t", pers, enableQlog, enableMetrics, enableCustomTracer)

		var tracerConstructors []func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer
		if enableQlog {
			tracerConstructors = append(tracerConstructors, func(_ context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
				if mrand.Int()%2 == 0 { // simulate that a qlog collector might only want to log some connections
					t.Logf("%s qlog tracer deciding to not trace connection %s", p, connID)
					return nil
				}
				t.Logf("%s qlog tracing connection %s", p, connID)
				return qlog.NewConnectionTracer(utils.NewBufferedWriteCloser(bufio.NewWriter(&bytes.Buffer{}), io.NopCloser(nil)), p, connID)
			})
		}
		if enableMetrics {
			tracerConstructors = append(tracerConstructors, metrics.DefaultConnectionTracer)
		}
		if enableCustomTracer {
			tracerConstructors = append(tracerConstructors, func(context.Context, logging.Perspective, quic.ConnectionID) *logging.ConnectionTracer {
				return &logging.ConnectionTracer{}
			})
		}
		c := conf.Clone()
		c.Tracer = func(ctx context.Context, p logging.Perspective, connID quic.ConnectionID) *logging.ConnectionTracer {
			tracers := make([]*logging.ConnectionTracer, 0, len(tracerConstructors))
			for _, c := range tracerConstructors {
				if tr := c(ctx, p, connID); tr != nil {
					tracers = append(tracers, tr)
				}
			}
			return logging.NewMultiplexedConnectionTracer(tracers...)
		}
		return c
	}

	for i := 0; i < 3; i++ {
		t.Run(fmt.Sprintf("run %d", i+1), func(t *testing.T) {
			if enableQlog {
				t.Skip("This test sets tracers and won't produce any qlogs.")
			}

			quicClientConf := addTracers(protocol.PerspectiveClient, getQuicConfig(nil))
			quicServerConf := addTracers(protocol.PerspectiveServer, getQuicConfig(nil))

			ln, err := quic.Listen(newUPDConnLocalhost(t), getTLSConfig(), quicServerConf)
			require.NoError(t, err)
			t.Cleanup(func() { require.NoError(t, ln.Close()) })

			var wg sync.WaitGroup
			for j := 0; j < 3; j++ {
				wg.Add(1)
				go func() {
					defer wg.Done()
					ctx, cancel := context.WithTimeout(context.Background(), time.Second)
					defer cancel()
					conn, err := quic.Dial(ctx, newUPDConnLocalhost(t), ln.Addr(), getTLSClientConfig(), quicClientConf)
					require.NoError(t, err)
					defer conn.CloseWithError(0, "")

					sconn, err := ln.Accept(ctx)
					if err != nil {
						return
					}
					sstr, err := sconn.OpenUniStream()
					require.NoError(t, err)
					_, err = sstr.Write(PRData)
					require.NoError(t, err)
					require.NoError(t, sstr.Close())

					str, err := conn.AcceptUniStream(ctx)
					require.NoError(t, err)
					data, err := io.ReadAll(str)
					require.NoError(t, err)
					require.Equal(t, PRData, data)
				}()
			}
			wg.Wait()
		})
	}
}
