package healthtiming

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"reflect"
	"strconv"
	"time"

	"code.justin.tv/d8a/pg-healthcheck/common"
	"code.justin.tv/d8a/pg-healthcheck/stats"
)

type StatQuery interface {
	Execute() (float64, time.Time, error)
}

func NewGraphiteQuery(graphiteURL string, cluster string, environment string, role stats.RoleContainer, host string, port int) StatQuery {
	return &graphiteQuery{
		graphiteURL: graphiteURL,
		cluster:     cluster,
		environment: environment,
		role:        role,
		host:        host,
		port:        strconv.Itoa(port),
	}
}

type graphiteQuery struct {
	graphiteURL string
	cluster     string
	environment string
	role        stats.RoleContainer
	host        string
	port        string
}

func (gq *graphiteQuery) Execute() (float64, time.Time, error) {
	if gq.role == nil || gq.role.Role() == "" {
		return 0, time.Unix(0, 0), errors.New("No role data available.")
	}
	url := fmt.Sprintf(
		"http://%s/render?target=divideSeries(movingAverage(stats.timers.pg-healthcheck.%s-%s.%s.%s.health.%s.*.total.mean,10),movingAverage(averageSeries(stats.timers.pg-healthcheck.%s-%s.%s.*.health.*.*.total.mean),10))&format=json&from=-10s",
		gq.graphiteURL,
		gq.cluster,
		gq.environment,
		gq.role.Role(),
		gq.host,
		gq.port,
		gq.cluster,
		gq.environment,
		gq.role.Role())

	resp, err := http.Get(url)
	if err != nil {
		return 0, time.Unix(0, 0), err
	}

	defer common.CloseResource(resp.Body)
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, time.Unix(0, 0), err
	}

	if resp.StatusCode/100 != 2 {
		return 0, time.Unix(0, 0), fmt.Errorf("Received %d response when accessing %s", resp.StatusCode, url)
	}

	graphiteData := make([]map[string]interface{}, 1)
	err = json.Unmarshal(body, &graphiteData)
	if err != nil {
		return 0, time.Unix(0, 0), err
	}

	if len(graphiteData) < 1 {
		return 0, time.Unix(0, 0), errors.New("No graphite metrics returned from the json API.")
	}

	metric := graphiteData[0]

	datapoints, ok := metric["datapoints"]
	if !ok {
		return 0, time.Unix(0, 0), errors.New("No graphite datapoints returned for the first metric from the json API.")
	}

	structDatapoints, ok := datapoints.([]interface{})
	if !ok {
		return 0, time.Unix(0, 0), errors.New("The graphite datapoints object was the wrong type: instead of []interface{}, was " + reflect.TypeOf(datapoints).String())
	}

	if len(structDatapoints) < 1 {
		return 0, time.Unix(0, 0), errors.New("No graphite datapoints returned for the first metric from the json API.")
	}

	singleDatapoint := structDatapoints[len(structDatapoints)-1]

	structDatapoint, ok := singleDatapoint.([]interface{})
	if !ok {
		return 0, time.Unix(0, 0), errors.New("The graphite single datapoint object wast he wrong type: instead of []interface{}, was " + reflect.TypeOf(singleDatapoint).String())
	}

	if len(structDatapoint) != 2 {
		return 0, time.Unix(0, 0), fmt.Errorf("Was expecting a single datapoint to be in the format [value, timestamp], instead got %v.", structDatapoint)
	}

	value, ok := structDatapoint[0].(float64)

	if !ok {
		return 0, time.Unix(0, 0), fmt.Errorf("Datapoint value was not a float32 as expected.  Instead was %v.", reflect.TypeOf(structDatapoint[0]))
	}

	timestamp, ok := structDatapoint[1].(float64)

	if !ok {
		return 0, time.Unix(0, 0), fmt.Errorf("Datapoint timestamp was not a float as expected.  Instead was %v: %v", reflect.TypeOf(structDatapoint[1]), structDatapoint[1])
	}

	return value, time.Unix(int64(timestamp), 0), nil
}
