package grafana

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"

	"github.com/grafana/grizzly/pkg/grizzly"
)

const smURL = "https:

func getRemoteCheck(uid string) (*grizzly.Resource, error) {
	url := getSyntheticMonitoringURL("api/v1/check/list")
	authToken, err := getAuthToken()
	if err != nil {
		return nil, err
	}
	client, err := NewHttpClient()
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer "+authToken)
	req.Header.Add("Content-type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	switch {
	case resp.StatusCode == http.StatusNotFound:
		return nil, grizzly.ErrNotFound
	case resp.StatusCode >= 400:
		return nil, errors.New(resp.Status)
	}

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var checks []Check
	if err := json.Unmarshal(data, &checks); err != nil {
		return nil, grizzly.APIErr{Err: err, Body: data}
	}
	probes, err := getProbeList()
	if err != nil {
		return nil, err
	}
	for _, check := range checks {
		if check.UID() == uid {
			probeNames := []string{}
			for _, probe := range check["probes"].([]interface{}) {
				probeID := int(probe.(float64))
				name := probes.ByID[probeID].Name
				probeNames = append(probeNames, name)
			}
			check["probes"] = probeNames
			handler := SyntheticMonitoringHandler{}
			resource := grizzly.NewResource(handler.APIVersion(), handler.Kind(), check.Job(), check)
			resource.SetMetadata("type", check.Type())
			return &resource, nil
		}
	}
	return nil, grizzly.ErrNotFound
}

func getRemoteCheckList() ([]string, error) {
	url := getSyntheticMonitoringURL("api/v1/check/list")
	authToken, err := getAuthToken()
	if err != nil {
		return nil, err
	}
	client, err := NewHttpClient()
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer "+authToken)
	req.Header.Add("Content-type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	switch {
	case resp.StatusCode == http.StatusNotFound:
		return nil, grizzly.ErrNotFound
	case resp.StatusCode >= 400:
		return nil, errors.New(resp.Status)
	}

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var checks []Check
	if err := json.Unmarshal(data, &checks); err != nil {
		return nil, grizzly.APIErr{Err: err, Body: data}
	}
	var checkIDs []string
	for _, check := range checks {
		checkIDs = append(checkIDs, check.UID())
	}
	return checkIDs, nil
}

func postCheck(url string, resource grizzly.Resource) error {
	check := Check(resource.Spec())
	checkJSON, err := check.toJSON()
	if err != nil {
		return err
	}

	client, err := NewHttpClient()
	if err != nil {
		return err
	}
	accessToken, err := getAuthToken()
	if err != nil {
		return err
	}
	req, err := http.NewRequest("POST", url, bytes.NewBufferString(checkJSON))
	if err != nil {
		return err
	}
	req.Header.Add("Authorization", "Bearer "+accessToken)
	req.Header.Add("Content-type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		return err
	}

	switch resp.StatusCode {
	case http.StatusOK:
		break
	default:
		return NewErrNon200Response("Synthetic Monitoring", resource.Name(), resp)
	}
	return nil
}

type Probe struct {
	ID       int    `json:"id"`
	TenantID int    `json:"tenantId"`
	Name     string `json:"name"`
	Region   string `json:"region"`
	Public   bool   `json:"public"`
	Online   bool   `json:"online"`
}

type Probes struct {
	ByID   map[int]Probe
	ByName map[string]Probe
}

func getProbeList() (*Probes, error) {
	url := getSyntheticMonitoringURL("api/v1/probe/list")
	authToken, err := getAuthToken()
	if err != nil {
		return nil, err
	}
	client, err := NewHttpClient()
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer "+authToken)

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusNotFound:
		return nil, grizzly.ErrNotFound
	default:
		if resp.StatusCode >= 400 {
			return nil, errors.New(resp.Status)
		}
	}

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	probeList := []Probe{}
	if err := json.Unmarshal(data, &probeList); err != nil {
		return nil, grizzly.APIErr{Err: err, Body: data}
	}
	probes := Probes{
		ByID:   map[int]Probe{},
		ByName: map[string]Probe{},
	}
	for _, probe := range probeList {
		if probe.Online && probe.Public {
			probes.ByID[probe.ID] = probe
			probes.ByName[probe.Name] = probe
		}
	}
	return &probes, nil
}

type Check map[string]interface{}

func (c *Check) Job() string {
	job, ok := (*c)["job"]
	if !ok {
		return ""
	}
	return job.(string)
}

func (c *Check) Type() string {
	settings, ok := (*c)["settings"]
	if !ok {
		return ""
	}
	for typ := range settings.(map[string]interface{}) {
		return typ
	}
	return ""
}

func (c *Check) UID() string {
	return fmt.Sprintf("%s.%s", c.Type(), c.Job())
}

func (c *Check) toJSON() (string, error) {
	probes, err := getProbeList()
	if err != nil {
		return "", err
	}
	probeIDs := []int{}
	for _, probe := range (*c)["probes"].([]interface{}) {
		probeName := probe.(string)
		id := probes.ByName[probeName].ID
		probeIDs = append(probeIDs, id)
	}
	(*c)["probes"] = probeIDs

	j, err := json.MarshalIndent(c, "", "  ")
	if err != nil {
		return "", err
	}
	return string(j), nil
}

func getSyntheticMonitoringURL(path string) string {
	return fmt.Sprintf(smURL, path)
}

func getAuthToken() (string, error) {
	url := getSyntheticMonitoringURL("api/v1/register/install")
	apiToken, ok := os.LookupEnv("GRAFANA_SM_TOKEN")
	if !ok {
		return "", fmt.Errorf("GRAFANA_SM_TOKEN environment variable must be set.")
	}

	stackID, err := strconv.Atoi(os.Getenv("GRAFANA_SM_STACK_ID"))
	if err != nil {
		return "", fmt.Errorf("GRAFANA_SM_STACK_ID environment variable must be set.")
	}
	metricsInstanceID, err := strconv.Atoi(os.Getenv("GRAFANA_SM_METRICS_ID"))
	if err != nil {
		return "", fmt.Errorf("GRAFANA_SM_METRICS_ID environment variable must be set.")
	}
	logsInstanceID, err := strconv.Atoi(os.Getenv("GRAFANA_SM_LOGS_ID"))
	if err != nil {
		return "", fmt.Errorf("GRAFANA_SM_LOGS_ID environment variable must be set.")
	}

	type AuthRequest struct {
		StackID           int `json:"stackId"`
		MetricsInstanceID int `json:"metricsInstanceId"`
		LogsInstanceID    int `json:"logsInstanceId"`
	}

	authRequest := AuthRequest{
		StackID:           stackID,
		MetricsInstanceID: metricsInstanceID,
		LogsInstanceID:    logsInstanceID,
	}

	authRequestJSON, err := json.Marshal(authRequest)
	if err != nil {
		return "", err
	}

	client, err := NewHttpClient()
	if err != nil {
		return "", err
	}
	req, err := http.NewRequest("POST", url, bytes.NewReader(authRequestJSON))
	req.Header.Add("Authorization", "Bearer "+apiToken)
	req.Header.Add("Content-type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		body, _ := ioutil.ReadAll(resp.Body)
		return "", fmt.Errorf("%d response while authenticating: %s", resp.StatusCode, string(body))
	}
	type AuthResponse struct {
		AccessToken string `json:"accessToken"`
	}
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	authResponse := AuthResponse{}
	if err := json.Unmarshal(data, &authResponse); err != nil {
		return "", grizzly.APIErr{Err: err, Body: data}

	}
	return authResponse.AccessToken, nil
}
package grafana

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"

	"github.com/grafana/grizzly/pkg/grizzly"
)

/*
 * @TODO
 * 1. The API does not have a GET method, so we have to fake it here
 * 2. The API expects an ID and a tenantId in an update, but these are
 *    generated by the server so cannot be represented in Jsonnet.
 *    Therefore, we have to pre-retrieve the check to get those values
 *    so we can inject them before posting JSON.
 * 3. This means pre-retrieving the check *twice*, once to establish
 *    whether this resource has changed or not (within Grizzly ifself)
 *    and again within this provider to retrieve IDs. Not ideal.
 * 4. The API expects probes to be specified by ID. This is not
 *    user-friendly. This code therefore takes in strings, and converts
 *    them to IDs, having requested an ID<->string mapping from the API.
 */

const smURL = "https://synthetic-monitoring-api.grafana.net/%s"

// getRemoteCheck retrieves a check object from SM
func getRemoteCheck(uid string) (*grizzly.Resource, error) {
	url := getSyntheticMonitoringURL("api/v1/check/list")
	authToken, err := getAuthToken()
	if err != nil {
		return nil, err
	}
	client, err := NewHttpClient()
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer "+authToken)
	req.Header.Add("Content-type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	switch {
	case resp.StatusCode == http.StatusNotFound:
		return nil, grizzly.ErrNotFound
	case resp.StatusCode >= 400:
		return nil, errors.New(resp.Status)
	}

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var checks []Check
	if err := json.Unmarshal(data, &checks); err != nil {
		return nil, grizzly.APIErr{Err: err, Body: data}
	}
	probes, err := getProbeList()
	if err != nil {
		return nil, err
	}
	for _, check := range checks {
		if check.UID() == uid {
			probeNames := []string{}
			for _, probe := range check["probes"].([]interface{}) {
				probeID := int(probe.(float64))
				name := probes.ByID[probeID].Name
				probeNames = append(probeNames, name)
			}
			check["probes"] = probeNames
			handler := SyntheticMonitoringHandler{}
			resource := grizzly.NewResource(handler.APIVersion(), handler.Kind(), check.Job(), check)
			resource.SetMetadata("type", check.Type())
			return &resource, nil
		}
	}
	return nil, grizzly.ErrNotFound
}

// getRemoteCheck retrieves a check object from SM
func getRemoteCheckList() ([]string, error) {
	url := getSyntheticMonitoringURL("api/v1/check/list")
	authToken, err := getAuthToken()
	if err != nil {
		return nil, err
	}
	client, err := NewHttpClient()
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer "+authToken)
	req.Header.Add("Content-type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	switch {
	case resp.StatusCode == http.StatusNotFound:
		return nil, grizzly.ErrNotFound
	case resp.StatusCode >= 400:
		return nil, errors.New(resp.Status)
	}

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var checks []Check
	if err := json.Unmarshal(data, &checks); err != nil {
		return nil, grizzly.APIErr{Err: err, Body: data}
	}
	var checkIDs []string
	for _, check := range checks {
		checkIDs = append(checkIDs, check.UID())
	}
	return checkIDs, nil
}

func postCheck(url string, resource grizzly.Resource) error {
	check := Check(resource.Spec())
	checkJSON, err := check.toJSON()
	if err != nil {
		return err
	}

	client, err := NewHttpClient()
	if err != nil {
		return err
	}
	accessToken, err := getAuthToken()
	if err != nil {
		return err
	}
	req, err := http.NewRequest("POST", url, bytes.NewBufferString(checkJSON))
	if err != nil {
		return err
	}
	req.Header.Add("Authorization", "Bearer "+accessToken)
	req.Header.Add("Content-type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		return err
	}

	switch resp.StatusCode {
	case http.StatusOK:
		break
	default:
		return NewErrNon200Response("Synthetic Monitoring", resource.Name(), resp)
	}
	return nil
}

// Probe defines the properties of a single SM Probe
type Probe struct {
	ID       int    `json:"id"`
	TenantID int    `json:"tenantId"`
	Name     string `json:"name"`
	Region   string `json:"region"`
	Public   bool   `json:"public"`
	Online   bool   `json:"online"`
}

// Probes allows accessing Probe objects by ID and by name
type Probes struct {
	ByID   map[int]Probe
	ByName map[string]Probe
}

// getRemoteCheck retrieves a check object from SM
func getProbeList() (*Probes, error) {
	url := getSyntheticMonitoringURL("api/v1/probe/list")
	authToken, err := getAuthToken()
	if err != nil {
		return nil, err
	}
	client, err := NewHttpClient()
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer "+authToken)

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusNotFound:
		return nil, grizzly.ErrNotFound
	default:
		if resp.StatusCode >= 400 {
			return nil, errors.New(resp.Status)
		}
	}

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	probeList := []Probe{}
	if err := json.Unmarshal(data, &probeList); err != nil {
		return nil, grizzly.APIErr{Err: err, Body: data}
	}
	probes := Probes{
		ByID:   map[int]Probe{},
		ByName: map[string]Probe{},
	}
	for _, probe := range probeList {
		if probe.Online && probe.Public {
			probes.ByID[probe.ID] = probe
			probes.ByName[probe.Name] = probe
		}
	}
	return &probes, nil
}

// Check encapsulates a check
type Check map[string]interface{}

// Job retrieves the job name from a check
func (c *Check) Job() string {
	job, ok := (*c)["job"]
	if !ok {
		return ""
	}
	return job.(string)
}

func (c *Check) Type() string {
	settings, ok := (*c)["settings"]
	if !ok {
		return ""
	}
	for typ := range settings.(map[string]interface{}) {
		return typ
	}
	return ""
}

func (c *Check) UID() string {
	return fmt.Sprintf("%s.%s", c.Type(), c.Job())
}

// toJSON returns JSON for a datasource
func (c *Check) toJSON() (string, error) {
	probes, err := getProbeList()
	if err != nil {
		return "", err
	}
	probeIDs := []int{}
	for _, probe := range (*c)["probes"].([]interface{}) {
		probeName := probe.(string)
		id := probes.ByName[probeName].ID
		probeIDs = append(probeIDs, id)
	}
	(*c)["probes"] = probeIDs

	j, err := json.MarshalIndent(c, "", "  ")
	if err != nil {
		return "", err
	}
	return string(j), nil
}

func getSyntheticMonitoringURL(path string) string {
	return fmt.Sprintf(smURL, path)
}

func getAuthToken() (string, error) {
	url := getSyntheticMonitoringURL("api/v1/register/install")
	apiToken, ok := os.LookupEnv("GRAFANA_SM_TOKEN")
	if !ok {
		return "", fmt.Errorf("GRAFANA_SM_TOKEN environment variable must be set.")
	}

	stackID, err := strconv.Atoi(os.Getenv("GRAFANA_SM_STACK_ID"))
	if err != nil {
		return "", fmt.Errorf("GRAFANA_SM_STACK_ID environment variable must be set.")
	}
	metricsInstanceID, err := strconv.Atoi(os.Getenv("GRAFANA_SM_METRICS_ID"))
	if err != nil {
		return "", fmt.Errorf("GRAFANA_SM_METRICS_ID environment variable must be set.")
	}
	logsInstanceID, err := strconv.Atoi(os.Getenv("GRAFANA_SM_LOGS_ID"))
	if err != nil {
		return "", fmt.Errorf("GRAFANA_SM_LOGS_ID environment variable must be set.")
	}

	type AuthRequest struct {
		StackID           int `json:"stackId"`
		MetricsInstanceID int `json:"metricsInstanceId"`
		LogsInstanceID    int `json:"logsInstanceId"`
	}

	authRequest := AuthRequest{
		StackID:           stackID,
		MetricsInstanceID: metricsInstanceID,
		LogsInstanceID:    logsInstanceID,
	}

	authRequestJSON, err := json.Marshal(authRequest)
	if err != nil {
		return "", err
	}

	client, err := NewHttpClient()
	if err != nil {
		return "", err
	}
	req, err := http.NewRequest("POST", url, bytes.NewReader(authRequestJSON))
	req.Header.Add("Authorization", "Bearer "+apiToken)
	req.Header.Add("Content-type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		body, _ := ioutil.ReadAll(resp.Body)
		return "", fmt.Errorf("%d response while authenticating: %s", resp.StatusCode, string(body))
	}
	type AuthResponse struct {
		AccessToken string `json:"accessToken"`
	}
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	authResponse := AuthResponse{}
	if err := json.Unmarshal(data, &authResponse); err != nil {
		return "", grizzly.APIErr{Err: err, Body: data}

	}
	return authResponse.AccessToken, nil
}
