package mqtt

import (
	"errors"
	"fmt"
	"regexp"
	"sort"
	"strings"
	"text/template"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/internal"
)

var idRe = regexp.MustCompile(`([^a-z0-9]+)`)

func (m *MQTT) collectHomieDeviceMessages(topic string, metric telegraf.Metric) ([]message, string, error) {
	var messages []message

	// Check if the device-id is already registered
	if _, found := m.homieSeen[topic]; !found {
		deviceName, err := homieGenerate(m.homieDeviceNameGenerator, metric)
		if err != nil {
			return nil, "", fmt.Errorf("generating device name failed: %w", err)
		}
		messages = append(messages,
			message{topic + "/$homie", []byte("4.0")},
			message{topic + "/$name", []byte(deviceName)},
			message{topic + "/$state", []byte("ready")},
		)
		m.homieSeen[topic] = make(map[string]bool)
	}

	// Generate the node-ID from the metric and fixup invalid characters
	nodeName, err := homieGenerate(m.homieNodeIDGenerator, metric)
	if err != nil {
		return nil, "", fmt.Errorf("generating device ID failed: %w", err)
	}
	nodeID := normalizeID(nodeName)

	if !m.homieSeen[topic][nodeID] {
		m.homieSeen[topic][nodeID] = true
		nodeIDs := make([]string, 0, len(m.homieSeen[topic]))
		for id := range m.homieSeen[topic] {
			nodeIDs = append(nodeIDs, id)
		}
		sort.Strings(nodeIDs)
		messages = append(messages,
			message{topic + "/$nodes", []byte(strings.Join(nodeIDs, ","))},
			message{topic + "/" + nodeID + "/$name", []byte(nodeName)},
		)
	}

	properties := make([]string, 0, len(metric.TagList())+len(metric.FieldList()))
	for _, tag := range metric.TagList() {
		properties = append(properties, normalizeID(tag.Key))
	}
	for _, field := range metric.FieldList() {
		properties = append(properties, normalizeID(field.Key))
	}
	sort.Strings(properties)

	messages = append(messages, message{
		topic + "/" + nodeID + "/$properties",
		[]byte(strings.Join(properties, ",")),
	})

	return messages, nodeID, nil
}

func normalizeID(raw string) string {
	// IDs in Home can only contain lowercase letters and hyphens
	// see https://homieiot.github.io/specification/#topic-ids
	id := strings.ToLower(raw)
	id = idRe.ReplaceAllString(id, "-")
	return strings.Trim(id, "-")
}

func convertType(value interface{}) (val, dtype string, err error) {
	v, err := internal.ToString(value)
	if err != nil {
		return "", "", err
	}

	switch value.(type) {
	case int8, int16, int32, int64, uint8, uint16, uint32, uint64:
		return v, "integer", nil
	case float32, float64:
		return v, "float", nil
	case []byte, string, fmt.Stringer:
		return v, "string", nil
	case bool:
		return v, "boolean", nil
	}
	return "", "", fmt.Errorf("unknown type %T", value)
}

func homieGenerate(t *template.Template, m telegraf.Metric) (string, error) {
	var b strings.Builder
	if err := t.Execute(&b, m.(telegraf.TemplateMetric)); err != nil {
		return "", err
	}

	result := b.String()
	if strings.Contains(result, "/") {
		return "", errors.New("cannot contain /")
	}

	return result, nil
}
