package fasthttp

import (
	"io"
	"net"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/valyala/fasthttp/fasthttputil"
)

func newFasthttpSleepEchoHandler(sleep time.Duration) RequestHandler {
	return func(ctx *RequestCtx) {
		time.Sleep(sleep)
		ctx.Success("text/plain", ctx.RequestURI())
	}
}

func BenchmarkClientGetEndToEndWaitConn1Inmemory(b *testing.B) {
	benchmarkClientGetEndToEndWaitConnInmemory(b, 1)
}

func BenchmarkClientGetEndToEndWaitConn10Inmemory(b *testing.B) {
	benchmarkClientGetEndToEndWaitConnInmemory(b, 10)
}

func BenchmarkClientGetEndToEndWaitConn100Inmemory(b *testing.B) {
	benchmarkClientGetEndToEndWaitConnInmemory(b, 100)
}

func BenchmarkClientGetEndToEndWaitConn1000Inmemory(b *testing.B) {
	benchmarkClientGetEndToEndWaitConnInmemory(b, 1000)
}

func benchmarkClientGetEndToEndWaitConnInmemory(b *testing.B, parallelism int) {
	ln := fasthttputil.NewInmemoryListener()

	ch := make(chan struct{})
	sleepDuration := 50 * time.Millisecond
	go func() {
		if err := Serve(ln, newFasthttpSleepEchoHandler(sleepDuration)); err != nil {
			b.Errorf("error when serving requests: %v", err)
		}
		close(ch)
	}()

	c := &Client{
		MaxConnsPerHost:    1,
		Dial:               func(addr string) (net.Conn, error) { return ln.Dial() },
		MaxConnWaitTimeout: 5 * time.Second,
	}

	requestURI := "/foo/bar?baz=123&sleep=10ms"
	url := "http://unused.host" + requestURI
	b.SetParallelism(parallelism)
	b.RunParallel(func(pb *testing.PB) {
		var buf []byte
		for pb.Next() {
			statusCode, body, err := c.Get(buf, url)
			if err != nil {
				if err != ErrNoFreeConns {
					b.Fatalf("unexpected error: %v", err)
				}
			} else {
				if statusCode != StatusOK {
					b.Fatalf("unexpected status code: %d. Expecting %d", statusCode, StatusOK)
				}
				if string(body) != requestURI {
					b.Fatalf("unexpected response %q. Expecting %q", body, requestURI)
				}
			}
			buf = body
		}
	})

	ln.Close()
	select {
	case <-ch:
	case <-time.After(time.Second):
		b.Fatalf("server wasn't stopped")
	}
}

func newNethttpSleepEchoHandler(sleep time.Duration) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(sleep)
		w.Header().Set(HeaderContentType, "text/plain")
		w.Write([]byte(r.RequestURI)) //nolint:errcheck
	}
}

func BenchmarkNetHTTPClientGetEndToEndWaitConn1Inmemory(b *testing.B) {
	benchmarkNetHTTPClientGetEndToEndWaitConnInmemory(b, 1)
}

func BenchmarkNetHTTPClientGetEndToEndWaitConn10Inmemory(b *testing.B) {
	benchmarkNetHTTPClientGetEndToEndWaitConnInmemory(b, 10)
}

func BenchmarkNetHTTPClientGetEndToEndWaitConn100Inmemory(b *testing.B) {
	benchmarkNetHTTPClientGetEndToEndWaitConnInmemory(b, 100)
}

func BenchmarkNetHTTPClientGetEndToEndWaitConn1000Inmemory(b *testing.B) {
	benchmarkNetHTTPClientGetEndToEndWaitConnInmemory(b, 1000)
}

func benchmarkNetHTTPClientGetEndToEndWaitConnInmemory(b *testing.B, parallelism int) {
	ln := fasthttputil.NewInmemoryListener()

	ch := make(chan struct{})
	sleep := 50 * time.Millisecond
	go func() {
		if err := http.Serve(ln, newNethttpSleepEchoHandler(sleep)); err != nil && !strings.Contains(
			err.Error(), "use of closed network connection") {
			b.Errorf("error when serving requests: %v", err)
		}
		close(ch)
	}()

	c := &http.Client{
		Transport: &http.Transport{
			Dial:            func(_, _ string) (net.Conn, error) { return ln.Dial() },
			MaxConnsPerHost: 1,
		},
		Timeout: 5 * time.Second,
	}

	requestURI := "/foo/bar?baz=123"
	url := "http://unused.host" + requestURI
	b.SetParallelism(parallelism)
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			resp, err := c.Get(url)
			if err != nil {
				if netErr, ok := err.(net.Error); !ok || !netErr.Timeout() {
					b.Fatalf("unexpected error: %v", err)
				}
			} else {
				if resp.StatusCode != http.StatusOK {
					b.Fatalf("unexpected status code: %d. Expecting %d", resp.StatusCode, http.StatusOK)
				}
				body, err := io.ReadAll(resp.Body)
				resp.Body.Close()
				if err != nil {
					b.Fatalf("unexpected error when reading response body: %v", err)
				}
				if string(body) != requestURI {
					b.Fatalf("unexpected response %q. Expecting %q", body, requestURI)
				}
			}
		}
	})

	ln.Close()
	select {
	case <-ch:
	case <-time.After(time.Second):
		b.Fatalf("server wasn't stopped")
	}
}
