package entrypoint_test

import (
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strconv"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/yusing/godoxy/internal/common"
	. "github.com/yusing/godoxy/internal/entrypoint"
	entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
	"github.com/yusing/godoxy/internal/route"
	routeTypes "github.com/yusing/godoxy/internal/route/types"
	"github.com/yusing/godoxy/internal/types"
	"github.com/yusing/goutils/task"
)

type noopResponseWriter struct {
	statusCode int
	written    []byte
}

func (w *noopResponseWriter) Header() http.Header {
	return http.Header{}
}

func (w *noopResponseWriter) Write(b []byte) (int, error) {
	w.written = b
	return len(b), nil
}

func (w *noopResponseWriter) WriteHeader(statusCode int) {
	w.statusCode = statusCode
}

type noopTransport struct{}

func (t noopTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	return &http.Response{
		StatusCode: http.StatusOK,
		Body:       io.NopCloser(strings.NewReader("1")),
		Request:    req,
		Header:     http.Header{},
	}, nil
}

func BenchmarkEntrypointReal(b *testing.B) {
	task := task.NewTestTask(b)
	ep := NewEntrypoint(task, nil)
	req := http.Request{
		Method: "GET",
		URL:    &url.URL{Path: "/", RawPath: "/"},
		Host:   "test.domain.tld",
	}
	ep.SetFindRouteDomains([]string{})
	entrypoint.SetCtx(task, ep)

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Length", "1")
		w.Write([]byte("1"))
	}))
	defer srv.Close()

	url, err := url.Parse(srv.URL)
	if err != nil {
		b.Fatal(err)
	}

	host, port, err := net.SplitHostPort(url.Host)
	if err != nil {
		b.Fatal(err)
	}

	portInt, err := strconv.Atoi(port)
	if err != nil {
		b.Fatal(err)
	}

	r, err := route.NewTestRoute(b, task, &route.Route{
		Alias:       "test",
		Scheme:      routeTypes.SchemeHTTP,
		Host:        host,
		Port:        route.Port{Proxy: portInt},
		HealthCheck: types.HealthCheckConfig{Disable: true},
	})

	require.NoError(b, err)
	require.False(b, r.ShouldExclude())

	var w noopResponseWriter

	server, ok := ep.GetServer(common.ProxyHTTPAddr)
	if !ok {
		b.Fatal("server not found")
	}

	b.ResetTimer()
	for b.Loop() {
		server.ServeHTTP(&w, &req)
		if w.statusCode != http.StatusOK {
			b.Fatalf("status code is not 200: %d", w.statusCode)
		}
		if string(w.written) != "1" {
			b.Fatalf("written is not 1: %s", string(w.written))
		}
	}
}

func BenchmarkEntrypoint(b *testing.B) {
	task := task.NewTestTask(b)
	ep := NewEntrypoint(task, nil)
	req := http.Request{
		Method: "GET",
		URL:    &url.URL{Path: "/", RawPath: "/"},
		Host:   "test.domain.tld",
	}
	ep.SetFindRouteDomains([]string{})
	entrypoint.SetCtx(task, ep)

	r, err := route.NewTestRoute(b, task, &route.Route{
		Alias:  "test",
		Scheme: routeTypes.SchemeHTTP,
		Host:   "localhost",
		Port: route.Port{
			Proxy: 8080,
		},
		HealthCheck: types.HealthCheckConfig{
			Disable: true,
		},
	})

	require.NoError(b, err)
	require.False(b, r.ShouldExclude())

	r.(types.ReverseProxyRoute).ReverseProxy().Transport = noopTransport{}

	var w noopResponseWriter

	server, ok := ep.GetServer(common.ProxyHTTPAddr)
	if !ok {
		b.Fatal("server not found")
	}

	b.ResetTimer()
	for b.Loop() {
		server.ServeHTTP(&w, &req)
		if w.statusCode != http.StatusOK {
			b.Fatalf("status code is not 200: %d", w.statusCode)
		}
	}
}
