package test

import (
	"bytes"
	"context"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// APICall is a small function object for testing HTTP APIs
type APICall struct {
	// http.Handler to run the call against
	Handler http.Handler

	// HTTP method, e.g. http.MethodPatch
	Method string

	// Request Path
	Path string

	// Request body. If nil, an empty body is sent
	RequestBody RequestBody

	// Request header. If nil, default header is sent
	Header http.Header

	// Request context. If nil, default context is used
	Context context.Context

	// Status that's expected to be received. If set to 0, the status is not checked
	ExpectedStatus int

	// Validator for the response body. If set to nil, the body is not validated
	ExpectedBody BodyValidator
}

// Do performs the request as defined in the APICall struct.
//
// If any errors occur when doing the request, or any of the validators fail, t.FailNow() is called
// Note that HTTP error status is not checked if ExpectedStatus == 0
//
// The result of the HTTP call is returned
func (a APICall) Do(t *testing.T) APICallResult {
	t.Helper()

	var bodyReader io.Reader
	if a.RequestBody != nil {
		bodyReader = bytes.NewReader(a.RequestBody.Body())
	}

	req := httptest.NewRequest(a.Method, a.Path, bodyReader)

	if a.Context != nil {
		req = req.WithContext(a.Context)
	}

	req.Header = a.Header
	if req.Header == nil {
		req.Header = http.Header{}
	}

	if a.RequestBody != nil && a.RequestBody.ContentType() != "" {
		req.Header.Set("Content-Type", a.RequestBody.ContentType())
	}
	respRecorder := httptest.NewRecorder()
	a.Handler.ServeHTTP(respRecorder, req)
	resp := respRecorder.Result()

	body, err := ioutil.ReadAll(resp.Body)
	require.NoErrorf(t, err, "%s: could not read response body", a.Path)

	if a.ExpectedStatus != 0 {
		assert.Equalf(t, a.ExpectedStatus, resp.StatusCode, "%s: SendHTTP failed for path", a.Path)
	}
	if a.ExpectedBody != nil {
		err = a.ExpectedBody.Validate(body)
		require.NoError(t, err, "%s: cannot validate response body", a.Path)
	}

	return APICallResult{
		Body:       body,
		StatusCode: resp.StatusCode,
	}
}

// APICallResult holds a parsed response for an APICall
type APICallResult struct {
	// Full body as read from the server
	Body []byte

	// Status code returned from the server
	StatusCode int
}
