package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"
)

// QueuedHTTPTester represents an HTTP transport tester that uses
// queued writes
type QueuedHTTPTester struct {
	client  http.Client
	url     string
	waitURL string
	br      *bytes.Reader
}

// NewQueuedHTTPTester returns an instantiated HTTP tester.
func NewQueuedHTTPTester(addr, path string) *QueuedHTTPTester {
	return &QueuedHTTPTester{
		client:  http.Client{},
		url:     fmt.Sprintf("http://%s%s?queue", addr, path),
		waitURL: fmt.Sprintf("http://%s%s?queue&wait", addr, path),
	}
}

// String returns a string representation of the tester.
func (h *QueuedHTTPTester) String() string {
	return h.url
}

// Prepare prepares the tester for execution.
func (h *QueuedHTTPTester) Prepare(stmt string, bSz int, _ bool) error {
	s := make([]string, bSz)
	for i := 0; i < len(s); i++ {
		s[i] = stmt
	}

	b, err := json.Marshal(s)
	if err != nil {
		return err
	}
	h.br = bytes.NewReader(b)

	return nil
}

// Once executes a single test request.
func (h *QueuedHTTPTester) Once() (time.Duration, error) {
	h.br.Seek(0, io.SeekStart)

	start := time.Now()
	resp, err := h.client.Post(h.url, "application/json", h.br)
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()

	_, err = io.ReadAll(resp.Body)
	if err != nil {
		return 0, err
	}

	if resp.StatusCode != http.StatusOK {
		return 0, fmt.Errorf("received %s", resp.Status)
	}
	dur := time.Since(start)

	return dur, nil
}

// Close closes the tester
func (h *QueuedHTTPTester) Close() error {
	start := time.Now()
	resp, err := h.client.Post(h.waitURL, "application/json", h.br)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Close() received %s", resp.Status)
	}
	fmt.Println("Queued tester wait request took", time.Since(start))
	return nil
}
