package builds

import (
	"bytes"
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"gitlab.com/gitlab-org/gitlab-workhorse/internal/redis"
)

const upstreamResponseCode = 999

func echoRequest(rw http.ResponseWriter, req *http.Request) {
	rw.WriteHeader(upstreamResponseCode)
	io.Copy(rw, req.Body)
}

var echoRequestFunc = http.HandlerFunc(echoRequest)

func expectHandlerWithWatcher(t *testing.T, watchHandler WatchKeyHandler, data string, contentType string, expectedHttpStatus int, msgAndArgs ...interface{}) {
	h := RegisterHandler(echoRequestFunc, watchHandler, time.Second)

	rw := httptest.NewRecorder()
	req, _ := http.NewRequest("POST", "/", bytes.NewBufferString(data))
	req.Header.Set("Content-Type", contentType)

	h.ServeHTTP(rw, req)

	require.Equal(t, expectedHttpStatus, rw.Code, msgAndArgs...)
}

func expectHandler(t *testing.T, data string, contentType string, expectedHttpStatus int, msgAndArgs ...interface{}) {
	expectHandlerWithWatcher(t, nil, data, contentType, expectedHttpStatus, msgAndArgs...)
}

func TestRegisterHandlerLargeBody(t *testing.T) {
	data := strings.Repeat(".", maxRegisterBodySize+5)
	expectHandler(t, data, "application/json", http.StatusRequestEntityTooLarge,
		"rejects body with entity too large")
}

func TestRegisterHandlerInvalidRunnerRequest(t *testing.T) {
	expectHandler(t, "invalid content", "text/plain", upstreamResponseCode,
		"proxies request to upstream")
}

func TestRegisterHandlerInvalidJsonPayload(t *testing.T) {
	expectHandler(t, `{[`, "application/json", upstreamResponseCode,
		"fails on parsing body and proxies request to upstream")
}

func TestRegisterHandlerMissingData(t *testing.T) {
	testCases := []string{
		`{"token":"token"}`,
		`{"last_update":"data"}`,
	}

	for _, testCase := range testCases {
		expectHandler(t, testCase, "application/json", upstreamResponseCode,
			"fails on argument validation and proxies request to upstream")
	}
}

func expectWatcherToBeExecuted(t *testing.T, watchKeyStatus redis.WatchKeyStatus, watchKeyError error,
	httpStatus int, msgAndArgs ...interface{}) {
	executed := false
	watchKeyHandler := func(key, value string, timeout time.Duration) (redis.WatchKeyStatus, error) {
		executed = true
		return watchKeyStatus, watchKeyError
	}

	parsableData := `{"token":"token","last_update":"last_update"}`

	expectHandlerWithWatcher(t, watchKeyHandler, parsableData, "application/json", httpStatus, msgAndArgs...)
	require.True(t, executed, msgAndArgs...)
}

func TestRegisterHandlerWatcherError(t *testing.T) {
	expectWatcherToBeExecuted(t, redis.WatchKeyStatusNoChange, errors.New("redis connection"),
		upstreamResponseCode, "proxies data to upstream")
}

func TestRegisterHandlerWatcherAlreadyChanged(t *testing.T) {
	expectWatcherToBeExecuted(t, redis.WatchKeyStatusAlreadyChanged, nil,
		upstreamResponseCode, "proxies data to upstream")
}

func TestRegisterHandlerWatcherSeenChange(t *testing.T) {
	expectWatcherToBeExecuted(t, redis.WatchKeyStatusSeenChange, nil,
		http.StatusNoContent)
}

func TestRegisterHandlerWatcherTimeout(t *testing.T) {
	expectWatcherToBeExecuted(t, redis.WatchKeyStatusTimeout, nil,
		http.StatusNoContent)
}

func TestRegisterHandlerWatcherNoChange(t *testing.T) {
	expectWatcherToBeExecuted(t, redis.WatchKeyStatusNoChange, nil,
		http.StatusNoContent)
}
