// Copyright 2020-2021 InfluxData, Inc. All rights reserved.
// Use of this source code is governed by MIT
// license that can be found in the LICENSE file.

// Package test provides shared test utils
package test

import (
	"compress/gzip"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
	"sync"
	"testing"

	http2 "github.com/influxdata/influxdb-client-go/v2/api/http"
	"github.com/stretchr/testify/assert"
)

// HTTPService is http.Service implementation for tests
type HTTPService struct {
	serverURL      string
	authorization  string
	lines          []string
	t              *testing.T
	wasGzip        bool
	requestHandler func(url string, body io.Reader) error
	replyError     *http2.Error
	lock           sync.Mutex
	requests       int
}

// WasGzip returns true of request was in GZip format
func (t *HTTPService) WasGzip() bool {
	return t.wasGzip
}

// SetWasGzip sets wasGzip flag
func (t *HTTPService) SetWasGzip(wasGzip bool) {
	t.wasGzip = wasGzip
}

// SetRequestHandler sets custom handler for requests
func (t *HTTPService) SetRequestHandler(fn func(url string, body io.Reader) error) {
	t.requestHandler = fn
}

// ServerURL returns testing URL
func (t *HTTPService) ServerURL() string {
	return t.serverURL
}

// ServerAPIURL returns testing URL
func (t *HTTPService) ServerAPIURL() string {
	return t.serverURL
}

// Authorization returns current authorization header value
func (t *HTTPService) Authorization() string {
	return t.authorization
}

// HTTPClient returns nil for this service
func (t *HTTPService) HTTPClient() *http.Client {
	return nil
}

// Requests returns number of requests
func (t *HTTPService) Requests() int {
	return t.requests
}

// Close clears instance
func (t *HTTPService) Close() {
	t.lock.Lock()
	if len(t.lines) > 0 {
		t.lines = t.lines[:0]
	}
	t.wasGzip = false
	t.replyError = nil
	t.requestHandler = nil
	t.requests = 0
	t.lock.Unlock()
}

// SetReplyError sets Error that will be returned as a response
func (t *HTTPService) SetReplyError(replyError *http2.Error) {
	t.lock.Lock()
	defer t.lock.Unlock()
	t.replyError = replyError
}

// ReplyError returns current reply error
func (t *HTTPService) ReplyError() *http2.Error {
	t.lock.Lock()
	defer t.lock.Unlock()
	return t.replyError
}

// SetAuthorization sets authorization string
func (t *HTTPService) SetAuthorization(_ string) {

}

// GetRequest does nothing for this service
func (t *HTTPService) GetRequest(_ context.Context, _ string, _ http2.RequestCallback, _ http2.ResponseCallback) *http2.Error {
	return nil
}

// DoHTTPRequest does nothing for this service
func (t *HTTPService) DoHTTPRequest(_ *http.Request, _ http2.RequestCallback, _ http2.ResponseCallback) *http2.Error {
	return nil
}

// DoHTTPRequestWithResponse does nothing for this service
func (t *HTTPService) DoHTTPRequestWithResponse(_ *http.Request, _ http2.RequestCallback) (*http.Response, error) {
	return nil, nil
}

// DoPostRequest reads http request, validates URL and stores data in the request
func (t *HTTPService) DoPostRequest(_ context.Context, url string, body io.Reader, requestCallback http2.RequestCallback, _ http2.ResponseCallback) *http2.Error {
	req, err := http.NewRequest("POST", url, nil)
	t.lock.Lock()
	t.requests++
	t.lock.Unlock()
	if err != nil {
		return http2.NewError(err)
	}
	if requestCallback != nil {
		requestCallback(req)
	}
	if req.Header.Get("Content-Encoding") == "gzip" {
		body, _ = gzip.NewReader(body)
		t.wasGzip = true
	}
	if t.t != nil {
		assert.Equal(t.t, fmt.Sprintf("%swrite?bucket=my-bucket&org=my-org&precision=ns", t.serverURL), url)
	}

	if t.ReplyError() != nil {
		return t.ReplyError()
	}
	if t.requestHandler != nil {
		err = t.requestHandler(url, body)
	} else {
		err = t.DecodeLines(body)
	}

	if err != nil {
		return http2.NewError(err)
	}
	return nil
}

// DecodeLines parses request body for lines
func (t *HTTPService) DecodeLines(body io.Reader) error {
	bytes, err := ioutil.ReadAll(body)
	if err != nil {
		return err
	}
	lines := strings.Split(string(bytes), "\n")
	if lines[len(lines)-1] == "" {
		lines = lines[:len(lines)-1]
	}
	t.lock.Lock()
	t.lines = append(t.lines, lines...)
	t.lock.Unlock()
	return nil
}

// Lines returns decoded lines from request
func (t *HTTPService) Lines() []string {
	t.lock.Lock()
	defer t.lock.Unlock()
	return t.lines
}

// NewTestService creates new test HTTP service
func NewTestService(t *testing.T, serverURL string) *HTTPService {
	return &HTTPService{
		t:         t,
		serverURL: serverURL + "/api/v2/",
	}
}
