package newrelic_platform_go

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
	"time"
)

const (
	NEWRELIC_API_URL = "https://platform-api.newrelic.com/platform/v1/metrics"
)

type INewrelicPlugin interface {
	GetMetricaKey(metrica IMetrica) string
	Harvest() error
	Run()
	AddComponent(component IComponent)
}
type NewrelicPlugin struct {
	Agent      *Agent          `json:"agent"`
	Components []ComponentData `json:"components"`

	// All HTTP requests will be done using this client. Change it if you need
	// to use a proxy.
	Client http.Client `json:"-"`

	// The URL to use for New Relic. Can change for testing purposes. Defaults to NERELIC_API_URL
	URL string

	ComponentModels      []IComponent `json:"-"`
	LastPollTime         time.Time    `json:"-"`
	Verbose              bool         `json:"-"`
	LicenseKey           string       `json:"-"`
	PollIntervalInSecond int          `json:"-"`
}

func NewNewrelicPlugin(version string, licenseKey string, pollInterval int) *NewrelicPlugin {
	plugin := &NewrelicPlugin{
		LicenseKey:           licenseKey,
		PollIntervalInSecond: pollInterval,
	}

	plugin.Agent = NewAgent(version)
	plugin.Agent.CollectEnvironmentInfo()
	plugin.URL = NEWRELIC_API_URL

	plugin.ComponentModels = []IComponent{}
	return plugin
}

func (plugin *NewrelicPlugin) Harvest() error {
	startTime := time.Now()
	var duration int
	if plugin.LastPollTime.IsZero() {
		duration = plugin.PollIntervalInSecond
	} else {
		duration = int(startTime.Sub(plugin.LastPollTime).Seconds())
	}

	plugin.Components = make([]ComponentData, 0, len(plugin.ComponentModels))
	for i := 0; i < len(plugin.ComponentModels); i++ {
		plugin.ComponentModels[i].SetDuration(duration)
		plugin.Components = append(plugin.Components, plugin.ComponentModels[i].Harvest(plugin))
	}

	if httpCode, err := plugin.SendMetricas(); err != nil {
		log.Printf("Can not send metricas to newrelic: %v\n", err)
		return err
	} else {

		if plugin.Verbose {
			log.Printf("Got HTTP response code:%d", httpCode)
		}

		if err, isFatal := plugin.CheckResponse(httpCode); isFatal {
			log.Printf("Got fatal error:%v\n", err)
			return err
		} else {
			if err != nil {
				log.Printf("WARNING: %v", err)
			}
			return err
		}
	}
	return nil
}

func (plugin *NewrelicPlugin) GetMetricaKey(metrica IMetrica) string {
	var keyBuffer bytes.Buffer

	keyBuffer.WriteString("Component/")
	keyBuffer.WriteString(metrica.GetName())
	keyBuffer.WriteString("[")
	keyBuffer.WriteString(metrica.GetUnits())
	keyBuffer.WriteString("]")

	return keyBuffer.String()
}

func (plugin *NewrelicPlugin) SendMetricas() (int, error) {
	var metricasJson []byte
	var encodingError error

	if plugin.Verbose {
		metricasJson, encodingError = json.MarshalIndent(plugin, "", "    ")
	} else {
		metricasJson, encodingError = json.Marshal(plugin)
	}

	if encodingError != nil {
		return 0, encodingError
	}

	jsonAsString := string(metricasJson)
	if plugin.Verbose {
		log.Printf("Send data:%s \n", jsonAsString)
	}

	if httpRequest, err := http.NewRequest("POST", plugin.URL, strings.NewReader(jsonAsString)); err != nil {
		return 0, err
	} else {
		httpRequest.Header.Set("X-License-Key", plugin.LicenseKey)
		httpRequest.Header.Set("Content-Type", "application/json")
		httpRequest.Header.Set("Accept", "application/json")

		if httpResponse, err := plugin.Client.Do(httpRequest); err != nil {
			return 0, err
		} else {
			defer httpResponse.Body.Close()
			return httpResponse.StatusCode, nil
		}
	}

	// we will never get there
	return 0, nil
}

func (plugin *NewrelicPlugin) ClearSentData() {
	for _, component := range plugin.ComponentModels {
		component.ClearSentData()
	}
	plugin.Components = nil
	plugin.LastPollTime = time.Now()
}

func (plugin *NewrelicPlugin) CheckResponse(httpResponseCode int) (error, bool) {
	isFatal := false
	var err error
	switch httpResponseCode {
	case http.StatusOK:
		{
			plugin.ClearSentData()
		}
	case http.StatusForbidden:
		{
			err = fmt.Errorf("Authentication error (no license key header, or invalid license key).\n")
			isFatal = true
		}
	case http.StatusBadRequest:
		{
			err = fmt.Errorf("The request or headers are in the wrong format or the URL is incorrect.\n")
			isFatal = true
		}
	case http.StatusNotFound:
		{
			err = fmt.Errorf("Invalid URL\n")
			isFatal = true
		}
	case http.StatusRequestEntityTooLarge:
		{
			err = fmt.Errorf("Too many metrics were sent in one request, or too many components (instances) were specified in one request, or other single-request limits were reached.\n")
			//discard metrics
			plugin.ClearSentData()
		}
	case http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
		{
			err = fmt.Errorf("Got %v response code.Metricas will be aggregated", httpResponseCode)
		}
	}
	return err, isFatal
}

func (plugin *NewrelicPlugin) Run() {
	plugin.Harvest()
	tickerChannel := time.Tick(time.Duration(plugin.PollIntervalInSecond) * time.Second)
	for ts := range tickerChannel {
		plugin.Harvest()

		if plugin.Verbose {
			log.Printf("Harvest ended at:%v\n", ts)
		}
	}
}

func (plugin *NewrelicPlugin) AddComponent(component IComponent) {
	plugin.ComponentModels = append(plugin.ComponentModels, component)
}
