package proxy_test

import (
	"crypto/rand"
	"fmt"
	"math/big"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/netbirdio/netbird/proxy/internal/proxy"
	"github.com/netbirdio/netbird/proxy/internal/types"
)

type nopTransport struct{}

func (nopTransport) RoundTrip(*http.Request) (*http.Response, error) {
	return &http.Response{
		StatusCode: http.StatusOK,
		Body:       http.NoBody,
	}, nil
}

func BenchmarkServeHTTP(b *testing.B) {
	rp := proxy.NewReverseProxy(nopTransport{}, "http", nil, nil)
	rp.AddMapping(proxy.Mapping{
		ID:        types.ServiceID(rand.Text()),
		AccountID: types.AccountID(rand.Text()),
		Host:      "app.example.com",
		Paths: map[string]*proxy.PathTarget{
			"/": {
				URL: &url.URL{
					Scheme: "http",
					Host:   "10.0.0.1:8080",
				},
			},
		},
	})

	req := httptest.NewRequest(http.MethodGet, "http://app.example.com", nil)
	req.Host = "app.example.com"
	req.RemoteAddr = "203.0.113.50:12345"

	for b.Loop() {
		rp.ServeHTTP(httptest.NewRecorder(), req)
	}
}

func BenchmarkServeHTTPHostCount(b *testing.B) {
	hostCounts := []int{1, 10, 100, 1_000, 10_000}

	for _, hostCount := range hostCounts {
		b.Run(fmt.Sprintf("hosts=%d", hostCount), func(b *testing.B) {
			rp := proxy.NewReverseProxy(nopTransport{}, "http", nil, nil)

			var target string
			targetIndex, err := rand.Int(rand.Reader, big.NewInt(int64(hostCount)))
			if err != nil {
				b.Fatal(err)
			}
			for i := range hostCount {
				id := rand.Text()
				host := fmt.Sprintf("%s.example.com", id)
				if int64(i) == targetIndex.Int64() {
					target = id
				}
				rp.AddMapping(proxy.Mapping{
					ID:        types.ServiceID(id),
					AccountID: types.AccountID(rand.Text()),
					Host:      host,
					Paths: map[string]*proxy.PathTarget{
						"/": {
							URL: &url.URL{
								Scheme: "http",
								Host:   "10.0.0.1:8080",
							},
						},
					},
				})
			}

			req := httptest.NewRequest(http.MethodGet, "http://"+target+"/", nil)
			req.Host = target
			req.RemoteAddr = "203.0.113.50:12345"

			for b.Loop() {
				rp.ServeHTTP(httptest.NewRecorder(), req)
			}
		})
	}
}

func BenchmarkServeHTTPPathCount(b *testing.B) {
	pathCounts := []int{1, 5, 10, 25, 50}

	for _, pathCount := range pathCounts {
		b.Run(fmt.Sprintf("paths=%d", pathCount), func(b *testing.B) {
			rp := proxy.NewReverseProxy(nopTransport{}, "http", nil, nil)

			var target string
			targetIndex, err := rand.Int(rand.Reader, big.NewInt(int64(pathCount)))
			if err != nil {
				b.Fatal(err)
			}

			paths := make(map[string]*proxy.PathTarget, pathCount)
			for i := range pathCount {
				path := "/" + rand.Text()
				if int64(i) == targetIndex.Int64() {
					target = path
				}
				paths[path] = &proxy.PathTarget{
					URL: &url.URL{
						Scheme: "http",
						Host:   "10.0.0.1:" + fmt.Sprintf("%d", 8080+i),
					},
				}
			}
			rp.AddMapping(proxy.Mapping{
				ID:        types.ServiceID(rand.Text()),
				AccountID: types.AccountID(rand.Text()),
				Host:      "app.example.com",
				Paths:     paths,
			})

			req := httptest.NewRequest(http.MethodGet, "http://app.example.com"+target, nil)
			req.Host = "app.example.com"
			req.RemoteAddr = "203.0.113.50:12345"

			for b.Loop() {
				rp.ServeHTTP(httptest.NewRecorder(), req)
			}
		})
	}
}
