package repro

import (
	"context"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

func withHTTP2Server(h http.Handler, do func(s *httptest.Server)) {
	s := httptest.NewUnstartedServer(h)
	s.TLS = &tls.Config{
		NextProtos: []string{"h2"},
	}
	s.Config.ErrorLog = log.New(ioutil.Discard, "", 0) // swallow the "bad certificate" log lines
	s.StartTLS()
	defer s.Close()

	transport := s.Client().Transport.(*http.Transport)
	clientConfig := transport.TLSClientConfig
	transport.TLSClientConfig = nil

	// We're looking at how http/2 requests get queued on a single
	// connection. Force use of a single connection so we can examine
	// that queuing.
	transport.MaxConnsPerHost = 1

	// make a request to trigger HTTP/2 autoconfiguration
	resp, err := s.Client().Get(s.URL)
	if err == nil {
		resp.Body.Close()
	}
	// now allow the client to connect to the ad-hoc test server
	transport.TLSClientConfig.RootCAs = clientConfig.RootCAs

	do(s)
}

func BenchmarkQueuedRequests(b *testing.B) {
	testcase := func(idleRequestCount int) func(b *testing.B) {
		return func(b *testing.B) {
			allow := make(chan struct{})
			var starts int64
			h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if !r.ProtoAtLeast(2, 0) {
					b.Errorf("Request is not http/2: %q", r.Proto)
					return
				}
				atomic.AddInt64(&starts, 1)
				<-allow
			})

			withHTTP2Server(h, func(s *httptest.Server) {
				ctx := context.Background()
				ctx, cancel := context.WithCancel(ctx)
				defer cancel()

				// Set up some (variable) number of idle/outstanding requests

				var wg sync.WaitGroup
				for i := 0; i < idleRequestCount; i++ {
					req, err := http.NewRequest("GET", s.URL, nil)
					if err != nil {
						b.Fatalf("NewRequest: %s", err)
					}
					wg.Add(1)
					go func() {
						defer wg.Done()
						ctx, cancel := context.WithCancel(ctx)
						defer cancel()
						req = req.WithContext(ctx)
						resp, err := s.Client().Do(req)
						if err != nil {
							return
						}
						resp.Body.Close()
					}()
				}

				// Allow requests to settle
				time.Sleep(time.Second)

				// Measure how quickly we can create and cancel a marginal request
				// on the contended http/2 connection.

				b.ResetTimer()
				for i := 0; i < b.N; i++ {

					req, err := http.NewRequest("GET", s.URL, nil)
					if err != nil {
						b.Fatalf("NewRequest: %s", err)
					}

					ctx, cancel := context.WithTimeout(ctx, time.Millisecond)
					req = req.WithContext(ctx)

					resp, err := s.Client().Do(req)
					if err == nil {
						resp.Body.Close()
					}
					cancel()
				}
				b.StopTimer()

				close(allow)
				cancel()
				wg.Wait()
			})
		}
	}

	for idle := 100; idle < 120000; idle *= 2 {
		b.Run(fmt.Sprintf("idle=%d", idle), testcase(idle))
	}
}
