package form_urlencoded

import (
	"bytes"
	"fmt"
	"net/url"
	"strconv"
	"time"

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

var (
	// ErrNoMetric is returned when no metric is found in input line
	ErrNoMetric = fmt.Errorf("no metric in line")
)

// Parser decodes "application/x-www-form-urlencoded" data into metrics
type Parser struct {
	MetricName  string
	DefaultTags map[string]string
	TagKeys     []string
	AllowedKeys []string
}

// Parse converts a slice of bytes in "application/x-www-form-urlencoded" format into metrics
func (p Parser) Parse(buf []byte) ([]telegraf.Metric, error) {
	buf = bytes.TrimSpace(buf)
	if len(buf) == 0 {
		return make([]telegraf.Metric, 0), nil
	}

	values, err := url.ParseQuery(string(buf))
	if err != nil {
		return nil, err
	}

	if len(p.AllowedKeys) > 0 {
		values = p.filterAllowedKeys(values)
	}

	tags := p.extractTags(values)
	fields := p.parseFields(values)

	for key, value := range p.DefaultTags {
		tags[key] = value
	}

	metric, err := metric.New(p.MetricName, tags, fields, time.Now().UTC())
	if err != nil {
		return nil, err
	}

	return []telegraf.Metric{metric}, nil
}

// ParseLine delegates a single line of text to the Parse function
func (p Parser) ParseLine(line string) (telegraf.Metric, error) {
	metrics, err := p.Parse([]byte(line))
	if err != nil {
		return nil, err
	}

	if len(metrics) < 1 {
		return nil, ErrNoMetric
	}

	return metrics[0], nil
}

// SetDefaultTags sets the default tags for every metric
func (p *Parser) SetDefaultTags(tags map[string]string) {
	p.DefaultTags = tags
}

func (p Parser) filterAllowedKeys(original url.Values) url.Values {
	result := make(url.Values)

	for _, key := range p.AllowedKeys {
		value, exists := original[key]
		if !exists {
			continue
		}

		result[key] = value
	}

	return result
}

func (p Parser) extractTags(values url.Values) map[string]string {
	tags := make(map[string]string)
	for _, key := range p.TagKeys {
		value, exists := values[key]

		if !exists || len(key) == 0 {
			continue
		}

		tags[key] = value[0]
		delete(values, key)
	}

	return tags
}

func (p Parser) parseFields(values url.Values) map[string]interface{} {
	fields := make(map[string]interface{})

	for key, value := range values {
		if len(key) == 0 || len(value) == 0 {
			continue
		}

		field, err := strconv.ParseFloat(value[0], 64)
		if err != nil {
			continue
		}

		fields[key] = field
	}

	return fields
}