// SPDX-License-Identifier: GPL-3.0-or-later

package couchbase

import (
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
	"github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"

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

var (
	dataConfigJSON, _ = os.ReadFile("testdata/config.json")
	dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")

	dataVer660BucketsBasicStats, _ = os.ReadFile("testdata/6.6.0/buckets_basic_stats.json")
)

func Test_testDataIsValid(t *testing.T) {
	for name, data := range map[string][]byte{
		"dataConfigJSON":              dataConfigJSON,
		"dataConfigYAML":              dataConfigYAML,
		"dataVer660BucketsBasicStats": dataVer660BucketsBasicStats,
	} {
		require.NotNil(t, data, name)
	}
}

func TestCouchbase_ConfigurationSerialize(t *testing.T) {
	module.TestConfigurationSerialize(t, &Couchbase{}, dataConfigJSON, dataConfigYAML)
}

func TestCouchbase_Init(t *testing.T) {
	tests := map[string]struct {
		config   Config
		wantFail bool
	}{
		"success on default config": {
			config: New().Config,
		},
		"fails on unset 'URL'": {
			wantFail: true,
			config: Config{
				HTTP: web.HTTP{
					Request: web.Request{
						URL: "",
					},
				},
			},
		},
		"fails on invalid URL": {
			wantFail: true,
			config: Config{
				HTTP: web.HTTP{
					Request: web.Request{
						URL: "127.0.0.1:9090",
					},
				},
			},
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			cb := New()
			cb.Config = test.config

			if test.wantFail {
				assert.Error(t, cb.Init())
			} else {
				assert.NoError(t, cb.Init())
			}
		})
	}
}

func TestCouchbase_Check(t *testing.T) {
	tests := map[string]struct {
		prepare  func(*testing.T) (cb *Couchbase, cleanup func())
		wantFail bool
	}{
		"success on valid response v6.6.0": {
			prepare: prepareCouchbaseV660,
		},
		"fails on response with invalid data": {
			wantFail: true,
			prepare:  prepareCouchbaseInvalidData,
		},
		"fails on 404 response": {
			wantFail: true,
			prepare:  prepareCouchbase404,
		},
		"fails on connection refused": {
			wantFail: true,
			prepare:  prepareCouchbaseConnectionRefused,
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			cb, cleanup := test.prepare(t)
			defer cleanup()

			if test.wantFail {
				assert.Error(t, cb.Check())
			} else {
				assert.NoError(t, cb.Check())
			}
		})
	}
}

func TestCouchbase_Collect(t *testing.T) {
	tests := map[string]struct {
		prepare       func(t *testing.T) (cb *Couchbase, cleanup func())
		wantCollected map[string]int64
	}{
		"success on valid response v6.6.0": {
			prepare: prepareCouchbaseV660,
			wantCollected: map[string]int64{
				"bucket_beer-sample_data_used":                     13990431,
				"bucket_beer-sample_disk_fetches":                  1,
				"bucket_beer-sample_disk_used":                     27690472,
				"bucket_beer-sample_item_count":                    7303,
				"bucket_beer-sample_mem_used":                      34294872,
				"bucket_beer-sample_ops_per_sec":                   1100,
				"bucket_beer-sample_quota_percent_used":            32706,
				"bucket_beer-sample_vb_active_num_non_resident":    1,
				"bucket_gamesim-sample_data_used":                  5371804,
				"bucket_gamesim-sample_disk_fetches":               1,
				"bucket_gamesim-sample_disk_used":                  13821793,
				"bucket_gamesim-sample_item_count":                 586,
				"bucket_gamesim-sample_mem_used":                   29586696,
				"bucket_gamesim-sample_ops_per_sec":                1100,
				"bucket_gamesim-sample_quota_percent_used":         28216,
				"bucket_gamesim-sample_vb_active_num_non_resident": 1,
				"bucket_travel-sample_data_used":                   53865472,
				"bucket_travel-sample_disk_fetches":                1,
				"bucket_travel-sample_disk_used":                   62244260,
				"bucket_travel-sample_item_count":                  31591,
				"bucket_travel-sample_mem_used":                    54318184,
				"bucket_travel-sample_ops_per_sec":                 1100,
				"bucket_travel-sample_quota_percent_used":          51801,
				"bucket_travel-sample_vb_active_num_non_resident":  1,
			},
		},
		"fails on response with invalid data": {
			prepare: prepareCouchbaseInvalidData,
		},
		"fails on 404 response": {
			prepare: prepareCouchbase404,
		},
		"fails on connection refused": {
			prepare: prepareCouchbaseConnectionRefused,
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			cb, cleanup := test.prepare(t)
			defer cleanup()

			collected := cb.Collect()

			assert.Equal(t, test.wantCollected, collected)
			ensureCollectedHasAllChartsDimsVarsIDs(t, cb, collected)
		})
	}
}

func prepareCouchbaseV660(t *testing.T) (cb *Couchbase, cleanup func()) {
	t.Helper()
	srv := httptest.NewServer(http.HandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			_, _ = w.Write(dataVer660BucketsBasicStats)
		}))

	cb = New()
	cb.URL = srv.URL
	require.NoError(t, cb.Init())

	return cb, srv.Close
}

func prepareCouchbaseInvalidData(t *testing.T) (*Couchbase, func()) {
	t.Helper()
	srv := httptest.NewServer(http.HandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			_, _ = w.Write([]byte("hello and\n goodbye"))
		}))
	cb := New()
	cb.URL = srv.URL
	require.NoError(t, cb.Init())

	return cb, srv.Close
}

func prepareCouchbase404(t *testing.T) (*Couchbase, func()) {
	t.Helper()
	srv := httptest.NewServer(http.HandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusNotFound)
		}))
	cb := New()
	cb.URL = srv.URL
	require.NoError(t, cb.Init())

	return cb, srv.Close
}

func prepareCouchbaseConnectionRefused(t *testing.T) (*Couchbase, func()) {
	t.Helper()
	cb := New()
	cb.URL = "http://127.0.0.1:38001"
	require.NoError(t, cb.Init())

	return cb, func() {}
}

func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, cb *Couchbase, collected map[string]int64) {
	for _, chart := range *cb.Charts() {
		if chart.Obsolete {
			continue
		}
		for _, dim := range chart.Dims {
			_, ok := collected[dim.ID]
			assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", dim.ID, chart.ID)
		}
		for _, v := range chart.Vars {
			_, ok := collected[v.ID]
			assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", v.ID, chart.ID)
		}
	}
}
