package client_test

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/influxdata/influxdb/client"
)

func BenchmarkWrite(b *testing.B) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var data client.Response
		w.WriteHeader(http.StatusNoContent)
		_ = json.NewEncoder(w).Encode(data)
	}))
	defer ts.Close()

	u, _ := url.Parse(ts.URL)
	config := client.Config{URL: *u}
	c, err := client.NewClient(config)
	if err != nil {
		b.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}

	bp := client.BatchPoints{
		Points: []client.Point{
			{Fields: map[string]interface{}{"value": 101}}},
	}
	for i := 0; i < b.N; i++ {
		r, err := c.Write(bp)
		if err != nil {
			b.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
		}
		if r != nil {
			b.Fatalf("unexpected response. expected %v, actual %v", nil, r)
		}
	}
}

func BenchmarkUnmarshalJSON2Tags(b *testing.B) {
	var bp client.BatchPoints
	data := []byte(`
{
    "database": "foo",
    "retentionPolicy": "bar",
    "points": [
        {
            "name": "cpu",
            "tags": {
                "host": "server01",
                "region": "us-east1"
            },
            "time": 14244733039069373,
            "precision": "n",
            "fields": {
                    "value": 4541770385657154000
            }
        }
    ]
}
`)

	for i := 0; i < b.N; i++ {
		if err := json.Unmarshal(data, &bp); err != nil {
			b.Errorf("unable to unmarshal nanosecond data: %s", err.Error())
		}
		b.SetBytes(int64(len(data)))
	}
}

func BenchmarkUnmarshalJSON10Tags(b *testing.B) {
	var bp client.BatchPoints
	data := []byte(`
{
    "database": "foo",
    "retentionPolicy": "bar",
    "points": [
        {
            "name": "cpu",
            "tags": {
                "host": "server01",
                "region": "us-east1",
                "tag1": "value1",
                "tag2": "value2",
                "tag2": "value3",
                "tag4": "value4",
                "tag5": "value5",
                "tag6": "value6",
                "tag7": "value7",
                "tag8": "value8"
            },
            "time": 14244733039069373,
            "precision": "n",
            "fields": {
                    "value": 4541770385657154000
            }
        }
    ]
}
`)

	for i := 0; i < b.N; i++ {
		if err := json.Unmarshal(data, &bp); err != nil {
			b.Errorf("unable to unmarshal nanosecond data: %s", err.Error())
		}
		b.SetBytes(int64(len(data)))
	}
}

func TestNewClient(t *testing.T) {
	config := client.Config{}
	_, err := client.NewClient(config)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}
}

func TestClient_Ping(t *testing.T) {
	ts := emptyTestServer()
	defer ts.Close()

	u, _ := url.Parse(ts.URL)
	config := client.Config{URL: *u}
	c, err := client.NewClient(config)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}
	d, version, err := c.Ping()
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}
	if d.Nanoseconds() == 0 {
		t.Fatalf("expected a duration greater than zero.  actual %v", d.Nanoseconds())
	}
	if version != "x.x" {
		t.Fatalf("unexpected version.  expected %s,  actual %v", "x.x", version)
	}
}

func TestClient_Query(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var data client.Response
		w.WriteHeader(http.StatusOK)
		_ = json.NewEncoder(w).Encode(data)
	}))
	defer ts.Close()

	u, _ := url.Parse(ts.URL)
	config := client.Config{URL: *u}
	c, err := client.NewClient(config)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}

	query := client.Query{}
	_, err = c.Query(query)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}
}

func TestClient_Query_RP(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		params := r.URL.Query()
		if got, exp := params.Get("db"), "db0"; got != exp {
			t.Errorf("unexpected db query parameter: %s != %s", exp, got)
		}
		if got, exp := params.Get("rp"), "rp0"; got != exp {
			t.Errorf("unexpected rp query parameter: %s != %s", exp, got)
		}
		var data client.Response
		w.WriteHeader(http.StatusOK)
		_ = json.NewEncoder(w).Encode(data)
	}))
	defer ts.Close()

	u, _ := url.Parse(ts.URL)
	config := client.Config{URL: *u}
	c, err := client.NewClient(config)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}

	query := client.Query{
		Database:        "db0",
		RetentionPolicy: "rp0",
	}
	_, err = c.Query(query)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}
}

func TestClient_ChunkedQuery(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var data client.Response
		w.WriteHeader(http.StatusOK)
		enc := json.NewEncoder(w)
		_ = enc.Encode(data)
		_ = enc.Encode(data)
	}))
	defer ts.Close()

	u, _ := url.Parse(ts.URL)
	config := client.Config{URL: *u}
	c, err := client.NewClient(config)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}

	query := client.Query{Chunked: true}
	_, err = c.Query(query)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}
}

func TestClient_QueryContext(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var data client.Response
		w.WriteHeader(http.StatusOK)
		_ = json.NewEncoder(w).Encode(data)
	}))
	defer ts.Close()

	u, _ := url.Parse(ts.URL)
	config := client.Config{URL: *u}
	c, err := client.NewClient(config)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}

	query := client.Query{}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	_, err = c.QueryContext(ctx, query)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}
}

func TestClient_QueryContext_Cancelled(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var data client.Response
		w.WriteHeader(http.StatusOK)
		_ = json.NewEncoder(w).Encode(data)
	}))
	defer ts.Close()

	u, _ := url.Parse(ts.URL)
	config := client.Config{URL: *u}
	c, err := client.NewClient(config)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}

	query := client.Query{}

	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	_, err = c.QueryContext(ctx, query)
	if err == nil {
		t.Fatalf("Since context was cancelled an error was expected, but got nil.")
	}
}

func TestClient_ChunkedQuery_WithContext(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var data client.Response
		w.WriteHeader(http.StatusOK)
		enc := json.NewEncoder(w)
		_ = enc.Encode(data)
		_ = enc.Encode(data)
	}))
	defer ts.Close()

	u, _ := url.Parse(ts.URL)
	config := client.Config{URL: *u}
	c, err := client.NewClient(config)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}

	query := client.Query{Chunked: true}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	_, err = c.QueryContext(ctx, query)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}
}

func TestClient_BasicAuth(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		u, p, ok := r.BasicAuth()

		if !ok {
			t.Errorf("basic auth error")
		}
		if u != "username" {
			t.Errorf("unexpected username, expected %q, actual %q", "username", u)
		}
		if p != "password" {
			t.Errorf("unexpected password, expected %q, actual %q", "password", p)
		}
		w.WriteHeader(http.StatusNoContent)
	}))
	defer ts.Close()

	u, _ := url.Parse(ts.URL)
	u.User = url.UserPassword("username", "password")
	config := client.Config{URL: *u, Username: "username", Password: "password"}
	c, err := client.NewClient(config)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}

	_, _, err = c.Ping()
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}
}

func TestClient_Write(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		in, err := io.ReadAll(r.Body)
		if err != nil {
			t.Fatalf("unexpected error: %s", err)
		} else if have, want := strings.TrimSpace(string(in)), `m0,host=server01 v1=2,v2=2i,v3=2u,v4="foobar",v5=true 0`; have != want {
			t.Errorf("unexpected write protocol: %s != %s", have, want)
		}
		var data client.Response
		w.WriteHeader(http.StatusNoContent)
		_ = json.NewEncoder(w).Encode(data)
	}))
	defer ts.Close()

	u, _ := url.Parse(ts.URL)
	config := client.Config{URL: *u}
	c, err := client.NewClient(config)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}

	bp := client.BatchPoints{
		Points: []client.Point{
			{
				Measurement: "m0",
				Tags: map[string]string{
					"host": "server01",
				},
				Time: time.Unix(0, 0).UTC(),
				Fields: map[string]interface{}{
					"v1": float64(2),
					"v2": int64(2),
					"v3": uint64(2),
					"v4": "foobar",
					"v5": true,
				},
			},
		},
	}
	r, err := c.Write(bp)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}
	if r != nil {
		t.Fatalf("unexpected response. expected %v, actual %v", nil, r)
	}
}

func TestClient_UserAgent(t *testing.T) {
	receivedUserAgent := ""
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		receivedUserAgent = r.UserAgent()

		var data client.Response
		w.WriteHeader(http.StatusOK)
		_ = json.NewEncoder(w).Encode(data)
	}))
	defer ts.Close()

	_, err := http.Get(ts.URL)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}

	tests := []struct {
		name      string
		userAgent string
		expected  string
	}{
		{
			name:      "Empty user agent",
			userAgent: "",
			expected:  "InfluxDBClient",
		},
		{
			name:      "Custom user agent",
			userAgent: "Test Influx Client",
			expected:  "Test Influx Client",
		},
	}

	for _, test := range tests {
		u, _ := url.Parse(ts.URL)
		config := client.Config{URL: *u, UserAgent: test.userAgent}
		c, err := client.NewClient(config)
		if err != nil {
			t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
		}

		receivedUserAgent = ""
		query := client.Query{}
		_, err = c.Query(query)
		if err != nil {
			t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
		}
		if !strings.HasPrefix(receivedUserAgent, test.expected) {
			t.Fatalf("Unexpected user agent. expected %v, actual %v", test.expected, receivedUserAgent)
		}

		receivedUserAgent = ""
		bp := client.BatchPoints{}
		_, err = c.Write(bp)
		if err != nil {
			t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
		}
		if !strings.HasPrefix(receivedUserAgent, test.expected) {
			t.Fatalf("Unexpected user agent. expected %v, actual %v", test.expected, receivedUserAgent)
		}

		receivedUserAgent = ""
		_, _, err = c.Ping()
		if err != nil {
			t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
		}
		if receivedUserAgent != test.expected {
			t.Fatalf("Unexpected user agent. expected %v, actual %v", test.expected, receivedUserAgent)
		}
	}
}

func TestClient_Messages(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"results":[{"messages":[{"level":"warning","text":"deprecation test"}]}]}`))
	}))
	defer ts.Close()

	u, _ := url.Parse(ts.URL)
	config := client.Config{URL: *u}
	c, err := client.NewClient(config)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}

	query := client.Query{}
	resp, err := c.Query(query)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}

	if got, exp := len(resp.Results), 1; got != exp {
		t.Fatalf("unexpected number of results.  expected %v, actual %v", exp, got)
	}

	r := resp.Results[0]
	if got, exp := len(r.Messages), 1; got != exp {
		t.Fatalf("unexpected number of messages.  expected %v, actual %v", exp, got)
	}

	m := r.Messages[0]
	if got, exp := m.Level, "warning"; got != exp {
		t.Errorf("unexpected message level.  expected %v, actual %v", exp, got)
	}
	if got, exp := m.Text, "deprecation test"; got != exp {
		t.Errorf("unexpected message text.  expected %v, actual %v", exp, got)
	}
}

func TestPoint_UnmarshalEpoch(t *testing.T) {
	now := time.Now()
	tests := []struct {
		name      string
		epoch     int64
		precision string
		expected  time.Time
	}{
		{
			name:      "nanoseconds",
			epoch:     now.UnixNano(),
			precision: "n",
			expected:  now,
		},
		{
			name:      "microseconds",
			epoch:     now.Round(time.Microsecond).UnixNano() / int64(time.Microsecond),
			precision: "u",
			expected:  now.Round(time.Microsecond),
		},
		{
			name:      "milliseconds",
			epoch:     now.Round(time.Millisecond).UnixNano() / int64(time.Millisecond),
			precision: "ms",
			expected:  now.Round(time.Millisecond),
		},
		{
			name:      "seconds",
			epoch:     now.Round(time.Second).UnixNano() / int64(time.Second),
			precision: "s",
			expected:  now.Round(time.Second),
		},
		{
			name:      "minutes",
			epoch:     now.Round(time.Minute).UnixNano() / int64(time.Minute),
			precision: "m",
			expected:  now.Round(time.Minute),
		},
		{
			name:      "hours",
			epoch:     now.Round(time.Hour).UnixNano() / int64(time.Hour),
			precision: "h",
			expected:  now.Round(time.Hour),
		},
		{
			name:      "max int64",
			epoch:     9223372036854775807,
			precision: "n",
			expected:  time.Unix(0, 9223372036854775807),
		},
		{
			name:      "100 years from now",
			epoch:     now.Add(time.Hour * 24 * 365 * 100).UnixNano(),
			precision: "n",
			expected:  now.Add(time.Hour * 24 * 365 * 100),
		},
	}

	for _, test := range tests {
		t.Logf("testing %q\n", test.name)
		data := []byte(fmt.Sprintf(`{"time": %d, "precision":"%s"}`, test.epoch, test.precision))
		t.Logf("json: %s", string(data))
		var p client.Point
		err := json.Unmarshal(data, &p)
		if err != nil {
			t.Fatalf("unexpected error.  exptected: %v, actual: %v", nil, err)
		}
		if !p.Time.Equal(test.expected) {
			t.Fatalf("Unexpected time.  expected: %v, actual: %v", test.expected, p.Time)
		}
	}
}

func TestPoint_UnmarshalRFC(t *testing.T) {
	now := time.Now().UTC()
	tests := []struct {
		name     string
		rfc      string
		now      time.Time
		expected time.Time
	}{
		{
			name:     "RFC3339Nano",
			rfc:      time.RFC3339Nano,
			now:      now,
			expected: now,
		},
		{
			name:     "RFC3339",
			rfc:      time.RFC3339,
			now:      now.Round(time.Second),
			expected: now.Round(time.Second),
		},
	}

	for _, test := range tests {
		t.Logf("testing %q\n", test.name)
		ts := test.now.Format(test.rfc)
		data := []byte(fmt.Sprintf(`{"time": %q}`, ts))
		t.Logf("json: %s", string(data))
		var p client.Point
		err := json.Unmarshal(data, &p)
		if err != nil {
			t.Fatalf("unexpected error.  exptected: %v, actual: %v", nil, err)
		}
		if !p.Time.Equal(test.expected) {
			t.Fatalf("Unexpected time.  expected: %v, actual: %v", test.expected, p.Time)
		}
	}
}

func TestPoint_MarshalOmitempty(t *testing.T) {
	now := time.Now().UTC()
	tests := []struct {
		name     string
		point    client.Point
		now      time.Time
		expected string
	}{
		{
			name:     "all empty",
			point:    client.Point{Measurement: "cpu", Fields: map[string]interface{}{"value": 1.1}},
			now:      now,
			expected: `{"measurement":"cpu","fields":{"value":1.1}}`,
		},
		{
			name:     "with time",
			point:    client.Point{Measurement: "cpu", Fields: map[string]interface{}{"value": 1.1}, Time: now},
			now:      now,
			expected: fmt.Sprintf(`{"measurement":"cpu","time":"%s","fields":{"value":1.1}}`, now.Format(time.RFC3339Nano)),
		},
		{
			name:     "with tags",
			point:    client.Point{Measurement: "cpu", Fields: map[string]interface{}{"value": 1.1}, Tags: map[string]string{"foo": "bar"}},
			now:      now,
			expected: `{"measurement":"cpu","tags":{"foo":"bar"},"fields":{"value":1.1}}`,
		},
		{
			name:     "with precision",
			point:    client.Point{Measurement: "cpu", Fields: map[string]interface{}{"value": 1.1}, Precision: "ms"},
			now:      now,
			expected: `{"measurement":"cpu","fields":{"value":1.1},"precision":"ms"}`,
		},
	}

	for _, test := range tests {
		t.Logf("testing %q\n", test.name)
		b, err := json.Marshal(&test.point)
		if err != nil {
			t.Fatalf("unexpected error.  exptected: %v, actual: %v", nil, err)
		}
		if test.expected != string(b) {
			t.Fatalf("Unexpected result.  expected: %v, actual: %v", test.expected, string(b))
		}
	}
}

func TestEpochToTime(t *testing.T) {
	now := time.Now()

	tests := []struct {
		name      string
		epoch     int64
		precision string
		expected  time.Time
	}{
		{name: "nanoseconds", epoch: now.UnixNano(), precision: "n", expected: now},
		{name: "microseconds", epoch: now.Round(time.Microsecond).UnixNano() / int64(time.Microsecond), precision: "u", expected: now.Round(time.Microsecond)},
		{name: "milliseconds", epoch: now.Round(time.Millisecond).UnixNano() / int64(time.Millisecond), precision: "ms", expected: now.Round(time.Millisecond)},
		{name: "seconds", epoch: now.Round(time.Second).UnixNano() / int64(time.Second), precision: "s", expected: now.Round(time.Second)},
		{name: "minutes", epoch: now.Round(time.Minute).UnixNano() / int64(time.Minute), precision: "m", expected: now.Round(time.Minute)},
		{name: "hours", epoch: now.Round(time.Hour).UnixNano() / int64(time.Hour), precision: "h", expected: now.Round(time.Hour)},
	}

	for _, test := range tests {
		t.Logf("testing %q\n", test.name)
		tm, e := client.EpochToTime(test.epoch, test.precision)
		if e != nil {
			t.Fatalf("unexpected error: expected %v, actual: %v", nil, e)
		}
		if !tm.Equal(test.expected) {
			t.Fatalf("unexpected time: expected %v, actual %v", test.expected, tm)
		}
	}
}

// helper functions

func emptyTestServer() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(50 * time.Millisecond)
		w.Header().Set("X-Influxdb-Version", "x.x")
	}))
}

// Ensure that data with epoch times can be decoded.
func TestBatchPoints_Normal(t *testing.T) {
	var bp client.BatchPoints
	data := []byte(`
{
    "database": "foo",
    "retentionPolicy": "bar",
    "points": [
        {
            "name": "cpu",
            "tags": {
                "host": "server01"
            },
            "time": 14244733039069373,
            "precision": "n",
            "values": {
                    "value": 4541770385657154000
            }
        },
        {
            "name": "cpu",
             "tags": {
                "host": "server01"
            },
            "time": 14244733039069380,
            "precision": "n",
            "values": {
                    "value": 7199311900554737000
            }
        }
    ]
}
`)

	if err := json.Unmarshal(data, &bp); err != nil {
		t.Errorf("unable to unmarshal nanosecond data: %s", err.Error())
	}
}

func TestClient_Timeout(t *testing.T) {
	done := make(chan bool)
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		<-done
	}))
	defer ts.Close()
	defer func() { done <- true }()
	u, _ := url.Parse(ts.URL)
	config := client.Config{URL: *u, Timeout: 500 * time.Millisecond}
	c, err := client.NewClient(config)
	if err != nil {
		t.Fatalf("unexpected error. expected %v, actual %v", nil, err)
	}
	query := client.Query{}
	_, err = c.Query(query)
	if err == nil {
		t.Fatalf("unexpected success. expected timeout error")
	} else if !strings.Contains(err.Error(), "request canceled") &&
		!strings.Contains(err.Error(), "context deadline exceeded") &&
		!strings.Contains(err.Error(), "use of closed network connection") &&
		!strings.Contains(err.Error(), "Client.Timeout exceeded") {
		t.Fatalf("unexpected error. expected 'request canceled' error, got %v", err)
	}
}

func TestClient_NoTimeout(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping in short mode")
	}
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(1 * time.Second)
		var data client.Response
		w.WriteHeader(http.StatusOK)
		_ = json.NewEncoder(w).Encode(data)
	}))
	defer ts.Close()

	u, _ := url.Parse(ts.URL)
	config := client.Config{URL: *u}
	c, err := client.NewClient(config)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}

	query := client.Query{}
	_, err = c.Query(query)
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	}
}

func TestClient_ParseConnectionString(t *testing.T) {
	for _, tt := range []struct {
		addr string
		ssl  bool
		exp  string
	}{
		{
			addr: "localhost",
			exp:  "http://localhost:8086",
		},
		{
			addr: "localhost:8086",
			exp:  "http://localhost:8086",
		},
		{
			addr: "localhost:80",
			exp:  "http://localhost",
		},
		{
			addr: "localhost",
			exp:  "https://localhost:8086",
			ssl:  true,
		},
		{
			addr: "localhost:443",
			exp:  "https://localhost",
			ssl:  true,
		},
		{
			addr: "localhost:80",
			exp:  "https://localhost:80",
			ssl:  true,
		},
		{
			addr: "localhost:443",
			exp:  "http://localhost:443",
		},
		{
			addr: "192.168.2.13:8086/boom",
			exp:  "http://192.168.2.13:8086/boom",
		},
		{
			addr: "",
			exp:  "http://localhost:8086",
		},
	} {
		name := tt.addr
		if tt.ssl {
			name += "+ssl"
		}
		t.Run(name, func(t *testing.T) {
			u, err := client.ParseConnectionString(tt.addr, tt.ssl)
			if err != nil {
				t.Fatalf("unexpected error: %s", err)
			}

			if got, want := u.String(), tt.exp; got != want {
				t.Fatalf("unexpected connection string: got=%s want=%s", got, want)
			}
		})
	}
}

func TestClient_ParseConnectionString_IPv6(t *testing.T) {
	path := "[fdf5:9ede:1875:0:a9ee:a600:8fe3:d495]:8086"
	u, err := client.ParseConnectionString(path, false)
	if err != nil {
		t.Fatalf("unexpected error, expected %v, actual %v", nil, err)
	}
	if u.Host != path {
		t.Fatalf("ipv6 parse failed, expected %s, actual %s", path, u.Host)
	}
}

func TestClient_CustomCertificates(t *testing.T) {
	// generated with:
	// openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3650 -nodes -config influx.cnf
	// influx.cnf:
	// [req]
	// distinguished_name = req_distinguished_name
	// x509_extensions = v3_req
	// prompt = no
	// [req_distinguished_name]
	// C = US
	// ST = CA
	// L = San Francisco
	// O = InfluxDB
	// CN = github.com/influxdata
	// [v3_req]
	// keyUsage = keyEncipherment, dataEncipherment
	// extendedKeyUsage = serverAuth
	// subjectAltName = @alt_names
	// [alt_names]
	// IP.1 = 127.0.0.1
	//
	key := `
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDLswqKJLxfhBRi
4qdj7+jpBxTAi4MewrcMPp+9YlbLke3F7w2DPrZVkYVeWmg8LyTPAigrXeadK6hv
qjRr05a7sMc5+ynivGbWUySWT+u17V85x6VR5TMIkJEOqpiIU8aYk0l+3UcrzVjS
1QZCUBoxVwAVaSR6AXTA8YrVXdk/AI3f22dYiBjFmV4LJJkGjTaCnlDKu54hMU1t
WTyFcoY9TBzZ1XA+ng5RQ/QADeL2PYrTW4s/mLI3jfKKD53EI4uM2FjW37ZfuxTa
mhCR7/lxM4COg9K70y5uebfqJvuoXAwXLOzVbdfF5b9fJFbL67kaK2tiMT3Wt39m
hXzclLTDAgMBAAECggEAK8mpElkjRUUXPMqMQSdpYe5rv5g973bb8n3jyMpC7i/I
dSwWM4hfmbVWfhnhHk7kErvb9raQxGiGJLrp2eP6Gw69RPGA54SodpoY21cCzHDi
b4FDQH+MoOKyy/xQHb4kitfejK70ha320huI5OhjOQgCtJeNh8yYVIGX3pX2BVyu
36UB9tfX1S5pbiHeih3vZGd322Muj/joNzIelnYRBnoO0xqvQ0S1Dk+dLCTHO0/m
u9AZN8c2TsRWZpJPMWwBv8LuABbE0e66/TSsrfklAn86ELCo44lZURDE7uPZ4pIH
FWtmf+nW5Hy6aPhy60E40MqotlejhWwB3ktY/m3JAQKBgQDuB4nhxzJA9lH9EaCt
byvJ9wGVvI3k79hOwc/Z2R3dNe+Ma+TJy+aBppvsLF4qz83aWC+canyasbHcPNR/
vXQGlsgKfucrmd1PfMV7uvOIkfOjK0E6mRC+jMuKtNTQrdtM1BU/Z7LY0iy0fNJ6
aNqhFdlJmmk0g+4bR4SAWB6FkwKBgQDbE/7r1u+GdJk/mhdjTi1aegr9lXb0l7L6
BCvOYhs/Z/pXfsaYPSXhgk2w+LiGk6BaEA2/4Sr0YS2MAAaIhBVeFBIXVpNrXB3K
Yg1jOEeLQ3qoVBeJFhJNrN9ZQx33HANC1W/Y1apMwaYqCRUGVQkrdcsN2KNea1z0
3qeYeCCSEQKBgCKZKeuNfrp+k1BLnaVYAW9r3ekb7SwXyMM53LJ3oqWiz10D2c+T
OcAirYtYr59dcTiJlPIRcGcz6PxwQxsGOLU0eYM9CvEFfmutYS8o73ksbdOL2AFi
elKYOIXC3yQuATBbq3L56b8mXaUmd5mfYBgGCv1t2ljtzFBext248UbNAoGBAIv1
2V24YiwnH6THf/ucfVMZNx5Mt8OJivk5YvcmLDw05HWzc5LdNe89PP871z963u3K
5c3ZP4UC9INFnOboY3JIJkqsr9/d6NZcECt8UBDDmoAhwSt+Y1EmiUZQn7s4NUkk
bKE919/Ts6GVTc5O013lkkUVS0HOG4QBH1dEH6LRAoGAStl11WA9tuKXiBl5XG/C
cq9mFPNJK3pEgd6YH874vEnYEEqENR4MFK3uWXus9Nm+VYxbUbPEzFF4kpsfukDg
/JAVqY4lUam7g6fyyaoIIPQEp7jGjbsUf46IjnUjFcaojOugA3EAfn9awREUDuJZ
cvh4WzEegcExTppINW1NB5E=
-----END PRIVATE KEY-----
`
	cert := `
-----BEGIN CERTIFICATE-----
MIIDdjCCAl6gAwIBAgIJAMYGAwkxUV51MA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzERMA8G
A1UECgwISW5mbHV4REIxETAPBgNVBAMMCGluZmx1eGRiMB4XDTE1MTIyOTAxNTg1
NloXDTI1MTIyNjAxNTg1NlowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYw
FAYDVQQHDA1TYW4gRnJhbmNpc2NvMREwDwYDVQQKDAhJbmZsdXhEQjERMA8GA1UE
AwwIaW5mbHV4ZGIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLswqK
JLxfhBRi4qdj7+jpBxTAi4MewrcMPp+9YlbLke3F7w2DPrZVkYVeWmg8LyTPAigr
XeadK6hvqjRr05a7sMc5+ynivGbWUySWT+u17V85x6VR5TMIkJEOqpiIU8aYk0l+
3UcrzVjS1QZCUBoxVwAVaSR6AXTA8YrVXdk/AI3f22dYiBjFmV4LJJkGjTaCnlDK
u54hMU1tWTyFcoY9TBzZ1XA+ng5RQ/QADeL2PYrTW4s/mLI3jfKKD53EI4uM2FjW
37ZfuxTamhCR7/lxM4COg9K70y5uebfqJvuoXAwXLOzVbdfF5b9fJFbL67kaK2ti
MT3Wt39mhXzclLTDAgMBAAGjQzBBMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgQw
MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcN
AQELBQADggEBAJxgHeduV9q2BuKnrt+sjXLGn/HwbMbgGbgFK6kUKJBWtv6Pa7JJ
m4teDmTMWiaeB2g4N2bmaWTuEZzzShNKG5roFeWm1ilFMAyzkb+VifN4YuDKH62F
3e259qsytiGbbJF3F//4sjfMw8qZVEPvspG1zKsASo0PpSOOUFmxcj0oMAXhnMrk
rRcbk6fufhyq0iZGl8ZLKTCrkjk0b3qlNs6UaRD9/XBB59VlQ8I338sfjV06edwY
jn5Amab0uyoFNEp70Y4WGxrxUTS1GAC1LCA13S7EnidD440UrnWALTarjmHAK6aW
war3JNM1mGB3o2iAtuOJlFIKLpI1x+1e8pI=
-----END CERTIFICATE-----
`
	cer, err := tls.X509KeyPair([]byte(cert), []byte(key))

	if err != nil {
		t.Fatalf("Received error: %v", err)
	}

	server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var data client.Response
		w.WriteHeader(http.StatusOK)
		_ = json.NewEncoder(w).Encode(data)
	}))
	server.TLS = &tls.Config{Certificates: []tls.Certificate{cer}}
	server.TLS.BuildNameToCertificate()
	server.StartTLS()
	defer server.Close()

	certFile, _ := os.CreateTemp("", "influx-cert-")
	certFile.WriteString(cert)
	certFile.Close()
	defer os.Remove(certFile.Name())

	u, _ := url.Parse(server.URL)

	tests := []struct {
		name      string
		unsafeSsl bool
		expected  error
	}{
		{name: "validate certificates", unsafeSsl: false, expected: errors.New("error")},
		{name: "not validate certificates", unsafeSsl: true, expected: nil},
	}

	for _, test := range tests {
		config := client.Config{URL: *u, UnsafeSsl: test.unsafeSsl}
		c, err := client.NewClient(config)
		if err != nil {
			t.Fatalf("unexpected error. expected %v, actual %v", nil, err)
		}
		query := client.Query{}
		_, err = c.Query(query)

		if (test.expected == nil) != (err == nil) {
			t.Fatalf("%s: expected %v. got %v. unsafeSsl: %v", test.name, test.expected, err, test.unsafeSsl)
		}
	}
}

func TestChunkedResponse(t *testing.T) {
	s := `{"results":[{},{}]}{"results":[{}]}`
	r := client.NewChunkedResponse(strings.NewReader(s))
	resp, err := r.NextResponse()
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	} else if actual := len(resp.Results); actual != 2 {
		t.Fatalf("unexpected number of results.  expected %v, actual %v", 2, actual)
	}

	resp, err = r.NextResponse()
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	} else if actual := len(resp.Results); actual != 1 {
		t.Fatalf("unexpected number of results.  expected %v, actual %v", 1, actual)
	}

	resp, err = r.NextResponse()
	if err != nil {
		t.Fatalf("unexpected error.  expected %v, actual %v", nil, err)
	} else if resp != nil {
		t.Fatalf("unexpected response.  expected %v, actual %v", nil, resp)
	}
}

func TestClient_Proxy(t *testing.T) {
	pinged := false
	server := httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
		if got, want := req.URL.String(), "http://example.com:8086/ping"; got != want {
			t.Errorf("invalid url in request: got=%s want=%s", got, want)
		}
		resp.WriteHeader(http.StatusNoContent)
		pinged = true
	}))
	defer server.Close()

	proxyURL, _ := url.Parse(server.URL)
	c, err := client.NewClient(client.Config{
		URL: url.URL{
			Scheme: "http",
			Host:   "example.com:8086",
		},
		Proxy: http.ProxyURL(proxyURL),
	})
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	if _, _, err := c.Ping(); err != nil {
		t.Fatalf("could not ping server: %s", err)
	}

	if !pinged {
		t.Fatalf("no http request was received")
	}
}

func TestSplitPath(t *testing.T) {
	tests := map[string]struct {
		input         string
		expectedfirst string
		expectedrest  string
	}{
		"empty": {
			input:         "",
			expectedfirst: "",
			expectedrest:  "",
		},
		"noslash": {
			input:         "foo",
			expectedfirst: "foo",
			expectedrest:  "",
		},
		"ideal": {
			input:         "foo/bar/baz/cuux:8080",
			expectedfirst: "foo",
			expectedrest:  "bar/baz/cuux:8080",
		},
		"trailingslash": {
			input:         "foo/",
			expectedfirst: "foo",
			expectedrest:  "",
		},
		"onlyslash": {
			input:         "/",
			expectedfirst: "",
			expectedrest:  "",
		},
	}

	t.Parallel()
	for name, test := range tests {
		name, test := name, test
		t.Run(name, func(t *testing.T) {
			first, rest := client.SplitPath(test.input)

			if gotfirst, gotrest, expectedfirst, expectedrest := first, rest, test.expectedfirst, test.expectedrest; gotfirst != expectedfirst || gotrest != expectedrest {
				t.Fatalf("splitPath(%q) returned (%q, %q); expected (%q, %q)", test.input, gotfirst, gotrest, expectedfirst, expectedrest)
			}
		})
	}
}
