package fibaro

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"path"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

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

// TestUnauthorized validates that 401 (wrong credentials) is managed properly
func TestUnauthorized(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusUnauthorized)
	}))
	defer ts.Close()

	a := Fibaro{
		URL:      ts.URL,
		Username: "user",
		Password: "pass",
		client:   &http.Client{},
	}
	require.NoError(t, a.Init())

	var acc testutil.Accumulator
	err := acc.GatherError(a.Gather)
	require.Error(t, err)
}

// TestJSONSuccess validates that module works OK with valid JSON payloads
func TestJSONSuccess(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		payload := ""
		switch r.URL.Path {
		case "/api/sections":
			content, err := os.ReadFile(path.Join("testdata", "sections.json"))
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				t.Error(err)
				return
			}
			payload = string(content)
		case "/api/rooms":
			content, err := os.ReadFile(path.Join("testdata", "rooms.json"))
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				t.Error(err)
				return
			}
			payload = string(content)
		case "/api/devices":
			content, err := os.ReadFile(path.Join("testdata", "device_hc2.json"))
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				t.Error(err)
				return
			}
			payload = string(content)
		}
		w.WriteHeader(http.StatusOK)
		if _, err := fmt.Fprintln(w, payload); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			t.Error(err)
			return
		}
	}))
	defer ts.Close()

	a := Fibaro{
		URL:      ts.URL,
		Username: "user",
		Password: "pass",
		client:   &http.Client{},
	}
	require.NoError(t, a.Init())

	var acc testutil.Accumulator
	err := acc.GatherError(a.Gather)
	require.NoError(t, err)
	require.Equal(t, uint64(5), acc.NMetrics())

	expected := []telegraf.Metric{
		metric.New(
			"fibaro",
			map[string]string{
				"deviceId": "1",
				"section":  "Section 1",
				"room":     "Room 1",
				"name":     "Device 1",
				"type":     "com.fibaro.binarySwitch",
			},
			map[string]interface{}{
				"value": float64(0),
			},
			time.Unix(0, 0),
		),
		metric.New(
			"fibaro",
			map[string]string{
				"deviceId": "2",
				"section":  "Section 2",
				"room":     "Room 2",
				"name":     "Device 2",
				"type":     "com.fibaro.binarySwitch",
			},
			map[string]interface{}{
				"value": float64(1),
			},
			time.Unix(0, 0),
		),
		metric.New(
			"fibaro",
			map[string]string{
				"deviceId": "3",
				"section":  "Section 3",
				"room":     "Room 3",
				"name":     "Device 3",
				"type":     "com.fibaro.multilevelSwitch",
			},
			map[string]interface{}{
				"value": float64(67),
			},
			time.Unix(0, 0),
		),
		metric.New(
			"fibaro",
			map[string]string{
				"deviceId": "4",
				"section":  "Section 3",
				"room":     "Room 4",
				"name":     "Device 4",
				"type":     "com.fibaro.temperatureSensor",
			},
			map[string]interface{}{
				"batteryLevel": float64(100),
				"value":        float64(22.8),
			},
			time.Unix(0, 0),
		),
		metric.New(
			"fibaro",
			map[string]string{
				"deviceId": "5",
				"section":  "Section 3",
				"room":     "Room 4",
				"name":     "Device 5",
				"type":     "com.fibaro.FGRM222",
			},
			map[string]interface{}{
				"energy": float64(4.33),
				"power":  float64(0.7),
				"value":  float64(50),
				"value2": float64(75),
			},
			time.Unix(0, 0),
		),
	}

	testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}

func TestHC3JSON(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		payload := ""
		switch r.URL.Path {
		case "/api/sections":
			content, err := os.ReadFile(path.Join("testdata", "sections.json"))
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				t.Error(err)
				return
			}
			payload = string(content)
		case "/api/rooms":
			content, err := os.ReadFile(path.Join("testdata", "rooms.json"))
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				t.Error(err)
				return
			}
			payload = string(content)
		case "/api/devices":
			content, err := os.ReadFile(path.Join("testdata", "device_hc3.json"))
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				t.Error(err)
				return
			}
			payload = string(content)
		}
		w.WriteHeader(http.StatusOK)
		if _, err := fmt.Fprintln(w, payload); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			t.Error(err)
			return
		}
	}))
	defer ts.Close()

	a := Fibaro{
		URL:        ts.URL,
		Username:   "user",
		Password:   "pass",
		DeviceType: "HC3",
		client:     &http.Client{},
	}
	require.NoError(t, a.Init())

	var acc testutil.Accumulator
	err := acc.GatherError(a.Gather)
	require.NoError(t, err)
	require.Equal(t, uint64(5), acc.NMetrics())

	expected := []telegraf.Metric{
		metric.New(
			"fibaro",
			map[string]string{
				"deviceId": "1",
				"section":  "Section 1",
				"room":     "Room 1",
				"name":     "Device 1",
				"type":     "com.fibaro.binarySwitch",
			},
			map[string]interface{}{
				"value": float64(0),
			},
			time.Unix(0, 0),
		),
		metric.New(
			"fibaro",
			map[string]string{
				"deviceId": "2",
				"section":  "Section 2",
				"room":     "Room 2",
				"name":     "Device 2",
				"type":     "com.fibaro.binarySwitch",
			},
			map[string]interface{}{
				"value": float64(1),
			},
			time.Unix(0, 0),
		),
		metric.New(
			"fibaro",
			map[string]string{
				"deviceId": "3",
				"section":  "Section 3",
				"room":     "Room 3",
				"name":     "Device 3",
				"type":     "com.fibaro.multilevelSwitch",
			},
			map[string]interface{}{
				"value": float64(67),
			},
			time.Unix(0, 0),
		),
		metric.New(
			"fibaro",
			map[string]string{
				"deviceId": "4",
				"section":  "Section 3",
				"room":     "Room 4",
				"name":     "Device 4",
				"type":     "com.fibaro.temperatureSensor",
			},
			map[string]interface{}{
				"batteryLevel": float64(100),
				"value":        float64(22.8),
			},
			time.Unix(0, 0),
		),
		metric.New(
			"fibaro",
			map[string]string{
				"deviceId": "5",
				"section":  "Section 3",
				"room":     "Room 4",
				"name":     "Device 5",
				"type":     "com.fibaro.FGRM222",
			},
			map[string]interface{}{
				"energy": float64(4.33),
				"power":  float64(0.7),
				"value":  float64(34),
				"value2": float64(75),
			},
			time.Unix(0, 0),
		),
	}

	testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}

func TestInvalidDeviceType(t *testing.T) {
	a := Fibaro{
		DeviceType: "foobar",
	}
	require.Error(t, a.Init())
}
