package stressClient

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

// ###############################################
// A selection of methods to manage the write path
// ###############################################

// Packages up Package from channel in goroutine
func (sc *stressClient) spinOffWritePackage(p Package, serv int) {
	sc.Add(1)
	sc.wc.Increment()
	go func() {
		sc.retry(p, time.Duration(time.Nanosecond), serv)
		sc.Done()
		sc.wc.Decrement()
	}()
}

// Implements backoff and retry logic for 500 responses
func (sc *stressClient) retry(p Package, backoff time.Duration, serv int) {

	// Set Backoff Interval to 500ms
	backoffInterval := time.Duration(500 * time.Millisecond)

	// Arithmetic backoff for kicks
	bo := backoff + backoffInterval

	// Make the write request
	resp, elapsed, err := sc.prepareWrite(p.Body, serv)

	// Find number of times request has been retried
	numBackoffs := int(bo/backoffInterval) - 1

	// On 500 responses, resp == nil. This logic keeps program for panicing
	var statusCode int

	if resp == nil {
		statusCode = 500
	} else {
		statusCode = resp.StatusCode
	}

	// Make a point for reporting
	point := sc.writePoint(numBackoffs, p.StatementID, statusCode, elapsed, p.Tracer.Tags, len(p.Body))

	// Send the Response(point, tracer)
	sc.responseChan <- NewResponse(point, p.Tracer)

	// BatchInterval enforcement
	bi, _ := time.ParseDuration(sc.wdelay)
	time.Sleep(bi)

	// Retry if the statusCode was not 204 or the err != nil
	if !(statusCode == 204) || err != nil {
		// Increment the *Tracer waitgroup if we are going to retry the request
		p.Tracer.Add(1)
		// Log the error if there is one
		fmt.Println(err)
		// Backoff enforcement
		time.Sleep(bo)
		sc.retry(p, bo, serv)
	}

}

// Prepares to send the POST request
func (sc *stressClient) prepareWrite(points []byte, serv int) (*http.Response, time.Duration, error) {

	// Construct address string
	var writeTemplate string
	if sc.ssl {
		writeTemplate = "https://%v/write?db=%v&precision=%v&u=%v&p=%v"
	} else {
		writeTemplate = "http://%v/write?db=%v&precision=%v&u=%v&p=%v"
	}
	address := fmt.Sprintf(writeTemplate, sc.addresses[serv], sc.database, sc.precision, sc.username, sc.password)

	// Start timer
	t := time.Now()
	resp, err := makePost(address, bytes.NewBuffer(points))
	elapsed := time.Since(t)

	return resp, elapsed, err
}

// Send POST request, read it, and handle errors
func makePost(url string, points io.Reader) (*http.Response, error) {

	resp, err := http.Post(url, "text/plain", points)

	if err != nil {
		return resp, fmt.Errorf("Error making write POST request\n  error: %v\n  url: %v\n", err, url)
	}

	body, _ := io.ReadAll(resp.Body)

	if resp.StatusCode != 204 {
		return resp, fmt.Errorf("Write returned non-204 status code\n  StatusCode: %v\n  InfluxDB Error: %v\n", resp.StatusCode, string(body))
	}

	resp.Body.Close()

	return resp, nil
}
