package fireboard

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"time"

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

// Fireboard gathers statistics from the fireboard.io servers
type Fireboard struct {
	AuthToken   string            `toml:"auth_token"`
	URL         string            `toml:"url"`
	HTTPTimeout internal.Duration `toml:"http_timeout"`

	client *http.Client
}

// NewFireboard return a new instance of Fireboard with a default http client
func NewFireboard() *Fireboard {
	tr := &http.Transport{ResponseHeaderTimeout: time.Duration(3 * time.Second)}
	client := &http.Client{
		Transport: tr,
		Timeout:   time.Duration(4 * time.Second),
	}
	return &Fireboard{client: client}
}

// RTT fireboardStats represents the data that is received from Fireboard
type RTT struct {
	Temp       float64 `json:"temp"`
	Channel    int64   `json:"channel"`
	Degreetype int     `json:"degreetype"`
	Created    string  `json:"created"`
}

type fireboardStats struct {
	Title       string `json:"title"`
	UUID        string `json:"uuid"`
	Latesttemps []RTT  `json:"latest_temps"`
}

// A sample configuration to only gather stats from localhost, default port.
const sampleConfig = `
  ## Specify auth token for your account
  auth_token = "invalidAuthToken"
  ## You can override the fireboard server URL if necessary
  # url = https://fireboard.io/api/v1/devices.json
  ## You can set a different http_timeout if you need to
  ## You should set a string using an number and time indicator
  ## for example "12s" for 12 seconds.
  # http_timeout = "4s"
`

// SampleConfig Returns a sample configuration for the plugin
func (r *Fireboard) SampleConfig() string {
	return sampleConfig
}

// Description Returns a description of the plugin
func (r *Fireboard) Description() string {
	return "Read real time temps from fireboard.io servers"
}

// Init the things
func (r *Fireboard) Init() error {

	if len(r.AuthToken) == 0 {
		return fmt.Errorf("You must specify an authToken")
	}
	if len(r.URL) == 0 {
		r.URL = "https://fireboard.io/api/v1/devices.json"
	}
	// Have a default timeout of 4s
	if r.HTTPTimeout.Duration == 0 {
		r.HTTPTimeout.Duration = time.Second * 4
	}

	r.client.Timeout = r.HTTPTimeout.Duration

	return nil
}

// Gather Reads stats from all configured servers.
func (r *Fireboard) Gather(acc telegraf.Accumulator) error {

	// Perform the GET request to the fireboard servers
	req, err := http.NewRequest("GET", r.URL, nil)
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Token "+r.AuthToken)
	resp, err := r.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// Successful responses will always return status code 200
	if resp.StatusCode != http.StatusOK {
		if resp.StatusCode == http.StatusForbidden {
			return fmt.Errorf("fireboard server responded with %d [Forbidden], verify your authToken", resp.StatusCode)
		}
		return fmt.Errorf("fireboard responded with unexpected status code %d", resp.StatusCode)
	}
	// Decode the response JSON into a new stats struct
	var stats []fireboardStats
	if err := json.NewDecoder(resp.Body).Decode(&stats); err != nil {
		return fmt.Errorf("unable to decode fireboard response: %s", err)
	}
	// Range over all devices, gathering stats. Returns early in case of any error.
	for _, s := range stats {
		r.gatherTemps(s, acc)
	}
	return nil
}

// Return text description of degree type (scale)
func scale(n int) string {
	switch n {
	case 1:
		return "Celcius"
	case 2:
		return "Fahrenheit"
	default:
		return ""
	}
}

// Gathers stats from a single device, adding them to the accumulator
func (r *Fireboard) gatherTemps(s fireboardStats, acc telegraf.Accumulator) {
	// Construct lookup for scale values

	for _, t := range s.Latesttemps {
		tags := map[string]string{
			"title":   s.Title,
			"uuid":    s.UUID,
			"channel": strconv.FormatInt(t.Channel, 10),
			"scale":   scale(t.Degreetype),
		}
		fields := map[string]interface{}{
			"temperature": t.Temp,
		}
		acc.AddFields("fireboard", fields, tags)
	}
}

func init() {
	inputs.Add("fireboard", func() telegraf.Input {
		return NewFireboard()
	})
}
