package zabbix

import (
	"encoding/binary"
	"encoding/json"
	"net"
	"os"
	"sort"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/config"
	"github.com/influxdata/telegraf/testutil"
)

type zabbixRequestData struct {
	Host  string `json:"host"`
	Key   string `json:"key"`
	Value string `json:"value"`
	Clock int64  `json:"clock"`
}

type zabbixRequest struct {
	Request      string              `json:"request"`
	Data         []zabbixRequestData `json:"data"`
	Clock        int                 `json:"clock"`
	Host         string              `json:"host"`
	HostMetadata string              `json:"host_metadata"`
}

type zabbixLLDValue struct {
	Data []map[string]string `json:"data"`
}

func TestZabbix(t *testing.T) {
	hostname, err := os.Hostname()
	require.NoError(t, err)

	tests := map[string]struct {
		KeyPrefix             string
		AgentActive           bool
		SkipMeasurementPrefix bool
		telegrafMetrics       []telegraf.Metric
		zabbixMetrics         []zabbixRequestData
	}{
		"send one metric with one field and no extra tags, generates one zabbix metric": {
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostname",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  "hostname",
					Key:   "name.value",
					Value: "0",
					Clock: 1522082244,
				},
			},
		},
		"string values representing a float number should be sent in the exact same format": {
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostname",
					},
					map[string]interface{}{
						"value": "3.1415",
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  "hostname",
					Key:   "name.value",
					Value: "3.1415",
					Clock: 1522082244,
				},
			},
		},
		"send one metric with one string field and no extra tags, generates one zabbix metric": {
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostname",
					},
					map[string]interface{}{
						"value": "some value",
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  "hostname",
					Key:   "name.value",
					Value: "some value",
					Clock: 1522082244,
				},
			},
		},
		"boolean values are converted to 1 (true) or 0 (false)": {
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostname",
					},
					map[string]interface{}{
						"valueTrue":  true,
						"valueFalse": false,
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  "hostname",
					Key:   "name.valueTrue",
					Value: "true",
					Clock: 1522082244,
				},
				{
					Host:  "hostname",
					Key:   "name.valueFalse",
					Value: "false",
					Clock: 1522082244,
				},
			},
		},
		"invalid value data is ignored and not sent": {
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostname",
					},
					map[string]interface{}{
						"value": []int{1, 2},
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{},
		},
		"metrics without host tag use the system hostname": {
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{},
					map[string]interface{}{
						"value": "x",
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  hostname,
					Key:   "name.value",
					Value: "x",
					Clock: 1522082244,
				},
			},
		},
		"send one metric with extra tags, zabbix metric should be generated with a parameter": {
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostname",
						"foo":  "bar",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  "hostname",
					Key:   "name.value[bar]",
					Value: "0",
					Clock: 1522082244,
				},
			},
		},
		"send one metric with two extra tags, zabbix parameters should be alfabetically orderer": {
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{
						"host":   "hostname",
						"zparam": "last",
						"aparam": "first",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  "hostname",
					Key:   "name.value[first,last]",
					Value: "0",
					Clock: 1522082244,
				},
			},
		},
		"send one metric with two fields and no extra tags, generates two zabbix metrics": {
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostname",
					},
					map[string]interface{}{
						"valueA": int64(0),
						"valueB": int64(1),
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  "hostname",
					Key:   "name.valueA",
					Value: "0",
					Clock: 1522082244,
				},
				{
					Host:  "hostname",
					Key:   "name.valueB",
					Value: "1",
					Clock: 1522082244,
				},
			},
		},
		"send two metrics with one field and no extra tags, generates two zabbix metrics": {
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("nameA",
					map[string]string{
						"host": "hostname",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(1522082244, 0),
				),
				testutil.MustMetric("nameB",
					map[string]string{
						"host": "hostname",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  "hostname",
					Key:   "nameA.value",
					Value: "0",
					Clock: 1522082244,
				},
				{
					Host:  "hostname",
					Key:   "nameB.value",
					Value: "0",
					Clock: 1522082244,
				},
			},
		},
		"send two metrics with different hostname, generates two zabbix metrics for different hosts": {
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostnameA",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(1522082244, 0),
				),
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostnameB",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  "hostnameA",
					Key:   "name.value",
					Value: "0",
					Clock: 1522082244,
				},
				{
					Host:  "hostnameB",
					Key:   "name.value",
					Value: "0",
					Clock: 1522082244,
				},
			},
		},
		"if key_prefix is configured, zabbix metrics should have that prefix in the key": {
			KeyPrefix: "telegraf.",
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostname",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  "hostname",
					Key:   "telegraf.name.value",
					Value: "0",
					Clock: 1522082244,
				},
			},
		},
		"if skip_measurement_prefix is configured, zabbix metrics should have to skip that prefix in the key": {
			SkipMeasurementPrefix: true,
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostname",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  "hostname",
					Key:   "value",
					Value: "0",
					Clock: 1522082244,
				},
			},
		},
		"if AgentActive is configured, zabbix metrics should be sent respecting that protocol": {
			AgentActive: true,
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostname",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(1522082244, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  "hostname",
					Key:   "name.value",
					Value: "0",
					Clock: 1522082244,
				},
			},
		},
		"metrics should be time sorted, oldest to newest, to avoid zabbix doing extra work when generating trends": {
			telegrafMetrics: []telegraf.Metric{
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostnameD",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(4444444444, 0),
				),
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostnameC",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(3333333333, 0),
				),
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostnameA",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(1111111111, 0),
				),
				testutil.MustMetric("name",
					map[string]string{
						"host": "hostnameB",
					},
					map[string]interface{}{
						"value": int64(0),
					},
					time.Unix(2222222222, 0),
				),
			},
			zabbixMetrics: []zabbixRequestData{
				{
					Host:  "hostnameA",
					Key:   "name.value",
					Value: "0",
					Clock: 1111111111,
				},
				{
					Host:  "hostnameB",
					Key:   "name.value",
					Value: "0",
					Clock: 2222222222,
				},
				{
					Host:  "hostnameC",
					Key:   "name.value",
					Value: "0",
					Clock: 3333333333,
				},
				{
					Host:  "hostnameD",
					Key:   "name.value",
					Value: "0",
					Clock: 4444444444,
				},
			},
		},
	}

	for desc, test := range tests {
		t.Run(desc, func(t *testing.T) {
			// Simulate a Zabbix server to get the data sent. It has a timeout to avoid waiting forever.
			listener, err := net.Listen("tcp", "127.0.0.1:")
			require.NoError(t, err)
			defer listener.Close()

			z := &Zabbix{
				Address:               listener.Addr().String(),
				KeyPrefix:             test.KeyPrefix,
				HostTag:               "host",
				SkipMeasurementPrefix: test.SkipMeasurementPrefix,
				AgentActive:           test.AgentActive,
				LLDSendInterval:       config.Duration(10 * time.Minute),
				Log:                   testutil.Logger{},
			}
			require.NoError(t, z.Init())

			wg := sync.WaitGroup{}
			wg.Add(1)
			go func() {
				success := make(chan zabbixRequest, 1)

				go func() {
					success <- listenForZabbixMetric(t, listener, len(test.zabbixMetrics) == 0)
				}()

				// By default we use trappers
				requestType := "sender data"
				if test.AgentActive {
					requestType = "agent data"
				}

				select {
				case request := <-success:
					require.Equal(t, requestType, request.Request)
					compareData(t, test.zabbixMetrics, request.Data)
				case <-time.After(1 * time.Second):
					require.Empty(t, test.zabbixMetrics, "no metrics should be expected if the connection times out")
				}

				wg.Done()
			}()

			require.NoError(t, z.Write(test.telegrafMetrics))

			// Wait for zabbix server emulator to finish
			wg.Wait()
		})
	}
}

// TestLLD tests how LLD metrics are sent simulating the time passing.
// LLD is sent each LLDSendInterval. Only new data.
// LLD data is cleared LLDClearInterval.
func TestLLD(t *testing.T) {
	// Telegraf metric which will be sent repeatedly
	m := testutil.MustMetric(
		"name",
		map[string]string{"host": "hostA", "foo": "bar"},
		map[string]interface{}{"value": int64(0)},
		time.Unix(0, 0),
	)

	mNew := testutil.MustMetric(
		"name",
		map[string]string{"host": "hostA", "foo": "moo"},
		map[string]interface{}{"value": int64(0)},
		time.Unix(0, 0),
	)

	// Expected Zabbix metric generated
	zabbixMetric := zabbixRequestData{
		Host:  "hostA",
		Key:   "telegraf.name.value[bar]",
		Value: "0",
		Clock: 0,
	}

	// Expected Zabbix metric generated
	zabbixMetricNew := zabbixRequestData{
		Host:  "hostA",
		Key:   "telegraf.name.value[moo]",
		Value: "0",
		Clock: 0,
	}

	// Expected Zabbix LLD metric generated
	zabbixLLDMetric := zabbixRequestData{
		Host:  "hostA",
		Key:   "telegraf.lld.name.foo",
		Value: `{"data":[{"{#FOO}":"bar"}]}`,
		Clock: 0,
	}

	// Expected Zabbix LLD metric generated
	zabbixLLDMetricNew := zabbixRequestData{
		Host:  "hostA",
		Key:   "telegraf.lld.name.foo",
		Value: `{"data":[{"{#FOO}":"bar"},{"{#FOO}":"moo"}]}`,
		Clock: 0,
	}

	// Simulate a Zabbix server to get the data sent
	listener, err := net.Listen("tcp", "127.0.0.1:")
	require.NoError(t, err)
	defer listener.Close()

	z := &Zabbix{
		Address:          listener.Addr().String(),
		KeyPrefix:        "telegraf.",
		HostTag:          "host",
		LLDSendInterval:  config.Duration(10 * time.Minute),
		LLDClearInterval: config.Duration(1 * time.Hour),
		Log:              testutil.Logger{},
	}
	require.NoError(t, z.Init())

	wg := sync.WaitGroup{}
	wg.Add(1)

	// Read first packet with two metrics, then the first autoregister packet and the second autoregister packet.
	go func() {
		// First packet with metrics
		request := listenForZabbixMetric(t, listener, false)
		compareData(t, []zabbixRequestData{zabbixMetric}, request.Data)

		// Second packet, while time has not surpassed LLDSendInterval
		request = listenForZabbixMetric(t, listener, false)
		compareData(t, []zabbixRequestData{zabbixMetric}, request.Data)

		// Third packet, time has surpassed LLDSendInterval, metrics + LLD
		request = listenForZabbixMetric(t, listener, false)
		require.Len(t, request.Data, 2, "Expected 2 metrics")
		request.Data[1].Clock = 0 // Ignore lld request clock
		compareData(t, []zabbixRequestData{zabbixMetric, zabbixLLDMetric}, request.Data)

		// Fourth packet with metrics
		request = listenForZabbixMetric(t, listener, false)
		compareData(t, []zabbixRequestData{zabbixMetric}, request.Data)

		// Fifth packet, time has surpassed LLDSendInterval, metrics. No LLD as there is nothing new.
		request = listenForZabbixMetric(t, listener, false)
		compareData(t, []zabbixRequestData{zabbixMetric}, request.Data)

		// Sixth packet, new LLD info, but time has not surpassed LLDSendInterval
		request = listenForZabbixMetric(t, listener, false)
		compareData(t, []zabbixRequestData{zabbixMetricNew}, request.Data)

		// Seventh packet, time has surpassed LLDSendInterval, metrics + LLD.
		// Also, time has surpassed LLDClearInterval, so LLD is cleared.
		request = listenForZabbixMetric(t, listener, false)
		require.Len(t, request.Data, 2, "Expected 2 metrics")
		request.Data[1].Clock = 0 // Ignore lld request clock
		compareData(t, []zabbixRequestData{zabbixMetric, zabbixLLDMetricNew}, request.Data)

		// Eighth packet, time host not surpassed LLDSendInterval, just metrics.
		request = listenForZabbixMetric(t, listener, false)
		compareData(t, []zabbixRequestData{zabbixMetric}, request.Data)

		// Ninth packet, time has surpassed LLDSendInterval, metrics + LLD.
		// Just the info of the zabbixMetric as zabbixMetricNew has not been seen since LLDClearInterval.
		request = listenForZabbixMetric(t, listener, false)
		require.Len(t, request.Data, 2, "Expected 2 metrics")
		request.Data[1].Clock = 0 // Ignore lld request clock
		compareData(t, []zabbixRequestData{zabbixMetric, zabbixLLDMetric}, request.Data)

		wg.Done()
	}()

	// First packet
	require.NoError(t, z.Write([]telegraf.Metric{m}))

	// Second packet, while time has not surpassed LLDSendInterval
	require.NoError(t, z.Write([]telegraf.Metric{m}))

	// Simulate time passing for a new LLD send
	z.lldLastSend = time.Now().Add(-time.Duration(z.LLDSendInterval)).Add(-time.Millisecond)

	// Third packet, time has surpassed LLDSendInterval, metrics + LLD
	require.NoError(t, z.Write([]telegraf.Metric{m}))

	// Fourth packet
	require.NoError(t, z.Write([]telegraf.Metric{m}))

	// Simulate time passing for a new LLD send
	z.lldLastSend = time.Now().Add(-time.Duration(z.LLDSendInterval)).Add(-time.Millisecond)

	// Fifth packet, time has surpassed LLDSendInterval, metrics. No LLD as there is nothing new.
	require.NoError(t, z.Write([]telegraf.Metric{m}))

	// Sixth packet, new LLD info, but time has not surpassed LLDSendInterval
	require.NoError(t, z.Write([]telegraf.Metric{mNew}))

	// Simulate time passing for LLD clear
	z.lldLastSend = time.Now().Add(-time.Duration(z.LLDClearInterval)).Add(-time.Millisecond)

	// Seventh packet, time has surpassed LLDSendInterval and LLDClearInterval, metrics + LLD.
	// LLD will be cleared.
	require.NoError(t, z.Write([]telegraf.Metric{m}))

	// Eighth packet, time host not surpassed LLDSendInterval, just metrics.
	require.NoError(t, z.Write([]telegraf.Metric{m}))

	// Simulate time passing for a new LLD send
	z.lldLastSend = time.Now().Add(-time.Duration(z.LLDSendInterval)).Add(-time.Millisecond)

	// Ninth packet, time has surpassed LLDSendInterval, metrics + LLD.
	require.NoError(t, z.Write([]telegraf.Metric{m}))

	// Wait for zabbix server emulator to finish
	wg.Wait()
}

// TestAutoregister tests that autoregistration requests are sent to zabbix if enabled
func TestAutoregister(t *testing.T) {
	// Simulate a Zabbix server to get the data sent
	listener, err := net.Listen("tcp", "127.0.0.1:")
	require.NoError(t, err)
	defer listener.Close()

	z := &Zabbix{
		Address:                    listener.Addr().String(),
		KeyPrefix:                  "telegraf.",
		HostTag:                    "host",
		SkipMeasurementPrefix:      false,
		AgentActive:                false,
		Autoregister:               "xxx",
		AutoregisterResendInterval: config.Duration(time.Minute * 5),
		Log:                        testutil.Logger{},
	}
	require.NoError(t, z.Init())

	wg := sync.WaitGroup{}
	wg.Add(1)

	// Read first packet with two metrics, then the first autoregister packet and the second autoregister packet.
	go func() {
		// Accept packet with the two metrics sent
		_ = listenForZabbixMetric(t, listener, false)

		// Read the first autoregister packet
		request := listenForZabbixMetric(t, listener, false)
		require.Equal(t, "active checks", request.Request)
		require.Equal(t, "xxx", request.HostMetadata)

		hostsRegistered := []string{request.Host}

		// Read the second autoregister packet
		request = listenForZabbixMetric(t, listener, false)
		require.Equal(t, "active checks", request.Request)
		require.Equal(t, "xxx", request.HostMetadata)

		// Check we have received autoregistration for both hosts
		hostsRegistered = append(hostsRegistered, request.Host)
		require.ElementsMatch(t, []string{"hostA", "hostB"}, hostsRegistered)

		wg.Done()
	}()

	err = z.Write([]telegraf.Metric{
		testutil.MustMetric(
			"name",
			map[string]string{"host": "hostA"},
			map[string]interface{}{"value": int64(0)},
			time.Now(),
		),
		testutil.MustMetric(
			"name",
			map[string]string{"host": "hostB"},
			map[string]interface{}{"value": int64(0)},
			time.Now(),
		),
	})
	require.NoError(t, err)

	// Wait for zabbix server emulator to finish
	wg.Wait()
}

// compareData compares generated data with expected data ignoring slice order if all Clocks are
// the same.
// This is useful for metrics with several fields that should produce several Zabbix values that
// could not be sorted by clock
func compareData(t *testing.T, expected []zabbixRequestData, data []zabbixRequestData) {
	t.Helper()

	var clock int64

	sameClock := true

	// Check if all clocks are the same
	for i := 0; i < len(data); i++ {
		if i == 0 {
			clock = data[i].Clock
		} else if clock != data[i].Clock {
			sameClock = false

			break
		}
	}

	// Zabbix requests with LLD data contains a JSON value with an array of dictionaries.
	// That array order depends in the access to a map, so it does not have a defined order.
	// To compare the data, we need to sort the array of dictionaries.
	// Before comparing the requests, sort those values.
	// To detect if a request contains LLD data, try to unmarshal it to a ZabbixLLDValue.
	// If it could be unmarshalled, sort the slice and marshal it again.
	for i := 0; i < len(data); i++ {
		var lldValue zabbixLLDValue

		err := json.Unmarshal([]byte(data[i].Value), &lldValue)
		if err == nil {
			sort.Slice(lldValue.Data, func(i, j int) bool {
				// Generate a global order based on the keys and values present in the map
				keysValuesI := make([]string, 0, len(lldValue.Data[i])*2)
				keysValuesJ := make([]string, 0, len(lldValue.Data[j])*2)
				for k, v := range lldValue.Data[i] {
					keysValuesI = append(keysValuesI, k, v)
				}
				for k, v := range lldValue.Data[j] {
					keysValuesJ = append(keysValuesJ, k, v)
				}

				sort.Strings(keysValuesI)
				sort.Strings(keysValuesJ)

				return strings.Join(keysValuesI, "") < strings.Join(keysValuesJ, "")
			})
			sortedValue, err := json.Marshal(lldValue)
			require.NoError(t, err)

			data[i].Value = string(sortedValue)
		}
	}

	if sameClock {
		require.ElementsMatch(t, expected, data)
	} else {
		require.Equal(t, expected, data)
	}
}

// listenForZabbixMetric starts a TCP server listening for one Zabbix metric.
// ignoreAcceptError is used to ignore the error when the server is closed.
func listenForZabbixMetric(t *testing.T, listener net.Listener, ignoreAcceptError bool) zabbixRequest {
	t.Helper()

	conn, err := listener.Accept()
	if err != nil && ignoreAcceptError {
		return zabbixRequest{}
	}

	require.NoError(t, err)

	// Obtain request from the mock zabbix server
	// Read protocol header and version
	header := make([]byte, 5)
	_, err = conn.Read(header)
	require.NoError(t, err)

	// Read data length
	dataLengthRaw := make([]byte, 8)
	_, err = conn.Read(dataLengthRaw)
	require.NoError(t, err)

	dataLength := binary.LittleEndian.Uint64(dataLengthRaw)

	// Read data content
	content := make([]byte, dataLength)
	_, err = conn.Read(content)
	require.NoError(t, err)

	// The zabbix output checks that there are not errors
	// Simulated response from the server
	resp := []byte("ZBXD\x01\x00\x00\x00\x00\x00\x00\x00\x00{\"response\": \"success\", \"info\": \"\"}\n")
	_, err = conn.Write(resp)
	require.NoError(t, err)

	// Close connection after reading the client data
	conn.Close()

	// Strip zabbix header and get JSON request
	var request zabbixRequest
	require.NoError(t, json.Unmarshal(content, &request))

	return request
}

func TestBuildZabbixMetric(t *testing.T) {
	keyPrefix := "prefix."
	hostTag := "host"

	z := &Zabbix{
		KeyPrefix: keyPrefix,
		HostTag:   hostTag,
	}

	zm, err := z.buildZabbixMetric(testutil.MustMetric(
		"name",
		map[string]string{hostTag: "hostA", "foo": "bar", "a": "b"},
		map[string]interface{}{},
		time.Now()),
		"value",
		1,
	)
	require.NoError(t, err)
	require.Equal(t, keyPrefix+"name.value[b,bar]", zm.Key)

	zm, err = z.buildZabbixMetric(testutil.MustMetric(
		"name",
		map[string]string{hostTag: "hostA"},
		map[string]interface{}{},
		time.Now()),
		"value",
		1,
	)
	require.NoError(t, err)
	require.Equal(t, keyPrefix+"name.value", zm.Key)
}

func TestGetHostname(t *testing.T) {
	hostname, err := os.Hostname()
	require.NoError(t, err)

	tests := map[string]struct {
		HostTag string
		Host    string
		Tags    map[string]string
		Result  string
	}{
		"metric with host tag": {
			HostTag: "host",
			Tags: map[string]string{
				"host": "bar",
			},
			Result: "bar",
		},
		"metric with host tag changed": {
			HostTag: "source",
			Tags: map[string]string{
				"source": "bar",
			},
			Result: "bar",
		},
		"metric with no host tag": {
			Tags:   map[string]string{},
			Result: hostname,
		},
	}

	for desc, test := range tests {
		t.Run(desc, func(t *testing.T) {
			metric := testutil.MustMetric(
				"name",
				test.Tags,
				map[string]interface{}{},
				time.Now(),
			)

			host, err := getHostname(test.HostTag, metric)
			require.NoError(t, err)
			require.Equal(t, test.Result, host)
		})
	}
}
