package httpdemo

import (
	"context"
	"errors"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"net/http/httptrace"
	"testing"
	"time"
)

func router() http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		_, _ = io.WriteString(w, r.PathValue("id"))
	})
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("X-Demo", "middleware")
		mux.ServeHTTP(w, r)
	})
}

func TestRoutesAndReuse(t *testing.T) {
	s := httptest.NewServer(router())
	defer s.Close()
	tr := http.DefaultTransport.(*http.Transport).Clone()
	tr.Proxy = nil
	tr.MaxIdleConnsPerHost = 2
	tr.MaxConnsPerHost = 4
	tr.ResponseHeaderTimeout = time.Second
	defer tr.CloseIdleConnections()
	client := &http.Client{Transport: tr, Timeout: 2 * time.Second}
	for i := 0; i < 2; i++ {
		reused := make(chan bool, 1)
		trace := &httptrace.ClientTrace{GotConn: func(c httptrace.GotConnInfo) { reused <- c.Reused }}
		ctx := httptrace.WithClientTrace(context.Background(), trace)
		req, err := http.NewRequestWithContext(ctx, http.MethodGet, s.URL+"/users/42", nil)
		if err != nil {
			t.Fatal(err)
		}
		resp, err := client.Do(req)
		if err != nil {
			t.Fatal(err)
		}
		body, err := io.ReadAll(resp.Body)
		closeErr := resp.Body.Close()
		if err != nil || closeErr != nil {
			t.Fatalf("read=%v close=%v", err, closeErr)
		}
		if string(body) != "42" || resp.Header.Get("X-Demo") != "middleware" {
			t.Fatalf("body=%q", body)
		}
		if got := <-reused; i == 1 && !got {
			t.Fatal("second sequential HTTP/1.1 request did not reuse connection")
		}
	}
	resp, err := client.Post(s.URL+"/users/42", "text/plain", nil)
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 405 || resp.Header.Get("Allow") != "GET, HEAD" {
		t.Fatalf("status=%d allow=%q", resp.StatusCode, resp.Header.Get("Allow"))
	}
	t.Log("path parameter, middleware, 405/Allow, and sequential connection reuse verified")
}

func TestShutdownWaits(t *testing.T) {
	entered, release := make(chan struct{}), make(chan struct{})
	srv := &http.Server{ReadHeaderTimeout: time.Second, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		close(entered)
		select {
		case <-release:
			_, _ = io.WriteString(w, "finished")
		case <-r.Context().Done():
		}
	})}
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer srv.Close()
	serveDone := make(chan error, 1)
	go func() { serveDone <- srv.Serve(ln) }()
	client := &http.Client{Timeout: 3 * time.Second}
	defer client.CloseIdleConnections()
	requestDone := make(chan error, 1)
	go func() {
		resp, e := client.Get("http://" + ln.Addr().String())
		if e == nil {
			_, e = io.Copy(io.Discard, resp.Body)
			resp.Body.Close()
		}
		requestDone <- e
	}()
	select {
	case <-entered:
	case <-time.After(2 * time.Second):
		t.Fatal("handler did not enter")
	}
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	shutdownDone := make(chan error, 1)
	go func() { shutdownDone <- srv.Shutdown(ctx) }()
	select {
	case e := <-serveDone:
		if !errors.Is(e, http.ErrServerClosed) {
			t.Fatal(e)
		}
	case <-ctx.Done():
		t.Fatal(ctx.Err())
	}
	select {
	case e := <-shutdownDone:
		t.Fatalf("Shutdown returned before active handler finished: %v", e)
	default:
	}
	close(release)
	if e := <-requestDone; e != nil {
		t.Fatal(e)
	}
	if e := <-shutdownDone; e != nil {
		t.Fatal(e)
	}
	t.Log("Serve returned ErrServerClosed while Shutdown waited for the active request")
}
