package serverutil

import (
	"context"
	"crypto/rand"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"git.autistici.org/ai3/go-common/clientutil"
)

type TestRequest struct {
	Data []string `json:"data"`
}

type TestObject struct {
	Name      string    `json:"name"`
	Host      string    `json:"host"`
	Timestamp time.Time `json:"timestamp"`
	PubKey    []byte    `json:"pubkey"`
}

type TestResponse struct {
	Objects []*TestObject `json:"objects"`
}

func fastRandomBytes(n int) []byte {
	b := make([]byte, n)
	rand.Read(b) // nolint: errcheck
	return b
}

func makeTestHandler() http.HandlerFunc {
	// Generate a large-ish random response.
	var resp TestResponse
	now := time.Now()
	n := 256
	resp.Objects = make([]*TestObject, 0, n)
	for i := 0; i < n; i++ {
		resp.Objects = append(resp.Objects, &TestObject{
			Name:      fmt.Sprintf("test-object-%06d", i+1),
			Host:      "host-452-ff-bb",
			Timestamp: now,
			PubKey:    fastRandomBytes(256),
		})
	}

	return func(w http.ResponseWriter, httpReq *http.Request) {
		var req TestRequest
		if !DecodeJSONRequest(w, httpReq, &req) {
			return
		}
		EncodeJSONResponse(w, &resp)
	}
}

const apiPath = "/api/v1/random"

func makeTestRequest() *TestRequest {
	var req TestRequest
	n := 256
	req.Data = make([]string, 0, n)
	for i := 0; i < n; i++ {
		req.Data = append(req.Data, fmt.Sprintf("data-item-%06d", i))
	}
	return &req
}

func makeSingleRequest(backend clientutil.Backend, req *TestRequest) error {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	var resp TestResponse
	return backend.Call(ctx, "", apiPath, &req, &resp)
}

func runHTTPTest(t *testing.T, config *ServerConfig) {
	mux := http.NewServeMux()
	mux.HandleFunc(apiPath, makeTestHandler())

	h, _, err := config.buildHTTPHandler(mux)
	if err != nil {
		t.Fatal(err)
	}
	srv := httptest.NewServer(h)
	defer srv.Close()

	backend, err := clientutil.NewBackend(&clientutil.BackendConfig{
		URL: srv.URL,
	})
	if err != nil {
		t.Fatalf("NewBackend() error: %v", err)
	}
	defer backend.Close()

	if err := makeSingleRequest(backend, makeTestRequest()); err != nil {
		t.Fatal(err)
	}
}

func TestHTTP(t *testing.T) {
	runHTTPTest(t, &ServerConfig{})
}

func TestHTTP_Compression(t *testing.T) {
	runHTTPTest(t, &ServerConfig{
		EnableCompression: true,
	})
}

func BenchmarkLoad(b *testing.B) {
	mux := http.NewServeMux()
	mux.HandleFunc(apiPath, makeTestHandler())

	config := &ServerConfig{
		EnableCompression: true,
	}
	h, _, _ := config.buildHTTPHandler(mux)
	srv := httptest.NewServer(h)
	defer srv.Close()

	backend, err := clientutil.NewBackend(&clientutil.BackendConfig{
		URL: srv.URL,
	})
	if err != nil {
		b.Fatalf("NewBackend() error: %v", err)
	}
	defer backend.Close()

	req := makeTestRequest()

	// Run clients.
	b.SetParallelism(100)
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			makeSingleRequest(backend, req) // nolint: errcheck
		}
	})
}